diff --git a/.github/workflows/generate-docs.yml b/.github/workflows/generate-docs.yml new file mode 100644 index 0000000..e427ab2 --- /dev/null +++ b/.github/workflows/generate-docs.yml @@ -0,0 +1,52 @@ +name: Generate and Deploy Documentation + +on: + push: + branches: + - main + +permissions: + actions: read + pages: write + id-token: write + +concurrency: + group: "pages" + cancel-in-progress: false + +jobs: + publish-docs: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Setup .NET SDK + uses: actions/setup-dotnet@v3 + with: + dotnet-version: 9.x + + - name: Install DocFX + run: dotnet tool install -g docfx + + - name: Add DocFX to PATH + run: echo "$HOME/.dotnet/tools" >> $GITHUB_PATH + + - name: Generate documentation + run: | + cd docs + docfx metadata + docfx build + + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + path: '_site' + + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/README.md b/README.md index 14595ef..100ea8f 100644 --- a/README.md +++ b/README.md @@ -1,37 +1,43 @@ ![WebExpress](https://raw.githubusercontent.com/ReneSchwarzer/WebExpress/main/assets/banner.png) # WebExpress -WebExpress is a lightweight web server optimized for use in low-performance environments (e.g. Rasperry PI). By providing -a powerful plugin system and a comprehensive API, web applications can be easily and quickly integrated into a .net -language (e.g. C#). Some advantages of WebExpress are: +`WebExpress` is a lightweight web server optimized for use in low-performance environments (e.g. Raspberry PI). By providing a powerful plugin system and a comprehensive API, web applications can be easily and quickly integrated into a .net language (e.g. C#). Some advantages of `WebExpress` are: - It is easy to use. - It offers a variety of features and tools that can help you build and manage your website. - It is fast and efficient and can help you save time and money. - It is flexible and can be customized to meet your specific requirements. -The WebExpress family includes the following projects: +The `WebExpress` family includes the following projects: -- [WebExpress](https://github.com/ReneSchwarzer/WebExpress#readme) - The web server for WebExpress applications and the documentation. -- [WebExpress.WebCore](https://github.com/ReneSchwarzer/WebExpress.WebCore#readme) - The core for WebExpress applications. -- [WebExpress.WebUI](https://github.com/ReneSchwarzer/WebExpress.WebUI#readme) - Common templates and controls for WebExpress applications. -- [WebExpress.WebIndex](https://github.com/ReneSchwarzer/WebExpress.WebIndex#readme) - Reverse index for WebExpress applications. -- [WebExpress.WebApp](https://github.com/ReneSchwarzer/WebExpress.WebApp#readme) - Business application template for WebExpress applications. +- [WebExpress](https://github.com/ReneSchwarzer/WebExpress#readme) - The web server for `WebExpress` applications and the documentation. +- [WebExpress.WebCore](https://github.com/ReneSchwarzer/WebExpress.WebCore#readme) - The core for `WebExpress` applications. +- [WebExpress.WebUI](https://github.com/ReneSchwarzer/WebExpress.WebUI#readme) - Common templates and controls for `WebExpress` applications. +- [WebExpress.WebIndex](https://github.com/ReneSchwarzer/WebExpress.WebIndex#readme) - Reverse index for `WebExpress` applications. +- [WebExpress.WebApp](https://github.com/ReneSchwarzer/WebExpress.WebApp#readme) - Business application template for `WebExpress` applications. # WebExpress.WebCore -WebCore is part of the Webexpres family and includes the basic elements of a WebExpress application. +`WebCore` is part of the `WebExpress` family and includes the basic elements of a `WebExpress` application. # Download The current binaries are available for download [here](https://github.com/ReneSchwarzer/WebExpress/releases). # Start -To get started with WebExpress, use the following links and tutorials. +If you're looking to get started with `WebExpress`, we would recommend using the following documentation. It can help you understand the platform. -- [installation guide](https://github.com/ReneSchwarzer/WebExpress/blob/main/doc/installation_guide.md) -- [development guide](https://github.com/ReneSchwarzer/WebExpress/blob/main/doc/development_guide.md) +- [Installation Guide](https://github.com/ReneSchwarzer/WebExpress/blob/main/doc/installation_guide.md) +- [Development Guide](https://github.com/ReneSchwarzer/WebExpress/blob/main/doc/development_guide.md) +- [WebExpress.WebCore API Documentation](https://reneschwarzer.github.io/WebExpress.WebCore/) +- [WebExpress.WebUI API Documentation](https://reneschwarzer.github.io/WebExpress.WebUI/) +- [WebExpress.WebApp API Documentation](https://reneschwarzer.github.io/WebExpress.WebApp/) +- [WebExpress.WebIndex API Documentation](https://reneschwarzer.github.io/WebExpress.WebIndex/) + +# Learning +The following tutorials illustrate the essential techniques of `WebExpress`. These tutorials are designed to assist you, as a developer, in understanding the various aspects of `WebExpress`. Each tutorial provides a detailed, step-by-step guide that you can work through using an example. If you re interested in beginning the development of `WebExpress` components, we would recommend you to complete some of these tutorials. -## Tutorials - [HelloWorld](https://github.com/ReneSchwarzer/WebExpress.Tutorial.HelloWorld#readme) +- [WebApp](https://github.com/ReneSchwarzer/WebExpress.Tutorial.WebApp#readme) +- [WebIndex](https://github.com/ReneSchwarzer/WebExpress.Tutorial.WebIndex#readme) # Tags -#Raspberry #Raspbian #IoT #NETCore #WebExpress \ No newline at end of file +#WebCore #WebExpress #DotNet #NETCore diff --git a/docs/api/toc.yml b/docs/api/toc.yml new file mode 100644 index 0000000..20c32af --- /dev/null +++ b/docs/api/toc.yml @@ -0,0 +1,2 @@ +### YamlMime:TableOfContent +[] diff --git a/docs/assets/webexpress.ico b/docs/assets/webexpress.ico new file mode 100644 index 0000000..c0909c9 Binary files /dev/null and b/docs/assets/webexpress.ico differ diff --git a/docs/assets/webexpress.svg b/docs/assets/webexpress.svg new file mode 100644 index 0000000..e9243f5 --- /dev/null +++ b/docs/assets/webexpress.svg @@ -0,0 +1,40 @@ + + + + + + + + + + template + + + Tabelle.2 + + + + Tabelle.35 + + + + diff --git a/docs/docfx.json b/docs/docfx.json new file mode 100644 index 0000000..f71e14c --- /dev/null +++ b/docs/docfx.json @@ -0,0 +1,61 @@ +{ + "$schema": "https://raw.githubusercontent.com/dotnet/docfx/main/schemas/docfx.schema.json", + "metadata": [ + { + "src": [ + { + "files": [ + "src/WebExpress.WebCore/*.csproj" + ], + "src": "../" + } + ], + "dest": "api", + "outputFormat": "apiPage" + } + ], + "build": { + "content": [ + { + "files": [ "**/*.{md,yml}" ], + "exclude": [ "_site/**", "obj/**" ] + } + ], + "resource": [ + { + "files": [ "**/images/**", "**/media/**", "codesnippet/**" ], + "exclude": [ "_site/**", "obj/**" ] + }, + { + "files": [ "assets/webexpress.ico", "assets/webexpress.svg" ] + }, + { + "src": "../schemas", + "files": [ "**/*.json" ], + "dest": "schemas" + } + ], + "postProcessors": [ "ExtractSearchIndex" ], + "globalMetadata": { + "_appTitle": "WebExpress.WebCore", + "_appName": "WebExpress.WebCore", + "_appFaviconPath": "assets/webexpress.ico", + "_appLogoPath": "assets/webexpress.svg", + "pdf": false + }, + "markdownEngineProperties": { + "alerts": { + "TODO": "alert alert-secondary" + } + }, + "xref": [ + "../.xrefmap.json" + ], + "output": "../_site", + "template": [ + "default", + "modern", + "template" + ] + } +} diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..6a2e7de --- /dev/null +++ b/docs/index.md @@ -0,0 +1,28 @@ +![WebExpress](https://raw.githubusercontent.com/ReneSchwarzer/WebExpress/main/assets/banner.png) + +# WebExpress +WebExpress is a lightweight web server optimized for use in low-performance environments (e.g. Raspberry PI). By providing +a powerful plugin system and a comprehensive API, web applications can be easily and quickly integrated into a .NET +language (e.g. C#). Some advantages of WebExpress are: + +- It is easy to use. +- It offers a variety of features and tools that can help you build and manage your website. +- It is fast and efficient and can help you save time and money. +- It is flexible and can be customized to meet your specific requirements. + +The `WebExpress` family includes the following projects: + +- [WebExpress](https://github.com/ReneSchwarzer/WebExpress#readme) - The web server for `WebExpress` applications and the documentation. +- [WebExpress.WebCore](https://github.com/ReneSchwarzer/WebExpress.WebCore#readme) - The core for `WebExpress` applications. +- [WebExpress.WebUI](https://github.com/ReneSchwarzer/WebExpress.WebUI#readme) - Common templates and controls for `WebExpress` applications. +- [WebExpress.WebIndex](https://github.com/ReneSchwarzer/WebExpress.WebIndex#readme) - Reverse index for `WebExpress` applications. +- [WebExpress.WebApp](https://github.com/ReneSchwarzer/WebExpress.WebApp#readme) - Business application template for `WebExpress` applications. + +# WebExpress.WebCore +WebCore is part of the WebExpress family and includes the basic elements of a WebExpress application. + +# Download +The current binaries are available for download [here](https://github.com/ReneSchwarzer/WebExpress/releases). + +# Tags +#Raspberry #Raspbian #IoT #NETCore #WebExpress diff --git a/docs/template/dashboard.html.tmpl b/docs/template/dashboard.html.tmpl new file mode 100644 index 0000000..0288fe8 --- /dev/null +++ b/docs/template/dashboard.html.tmpl @@ -0,0 +1,47 @@ +{{!Licensed to the .NET Foundation under one or more agreements. The .NET Foundation licenses this file to you under the MIT license.}} +{{!master(layout/_master.tmpl)}} +

{{title}}

+{{#items.Length}} +
+{{#items}} +
+ {{name}} +
+
{{name}}
+

{{{description}}}

+
+
+ {{#usage}} +
+ {{#config}}
docfx.json: {{config}}
{{/config}} + {{#command}}
docfx: {{command}}
{{/command}} + {{#init}}
docfx init: {{init}}
{{/init}} +
+ {{/usage}} +
+
+{{/items}} +
+{{/items.Length}} + + diff --git a/docs/template/public/main.css b/docs/template/public/main.css new file mode 100644 index 0000000..b0b8c1f --- /dev/null +++ b/docs/template/public/main.css @@ -0,0 +1,10 @@ +/** + * Licensed to the .NET Foundation under one or more agreements. + * The .NET Foundation licenses this file to you under the MIT license. + */ + +/* Checkout https://getbootstrap.com/docs/5.3/customize/color/ for more customization options */ +body { + --bs-link-color-rgb: 66, 184, 131 !important; + --bs-link-hover-color-rgb: 64, 180, 128 !important; +} diff --git a/docs/template/public/main.js b/docs/template/public/main.js new file mode 100644 index 0000000..d5c4867 --- /dev/null +++ b/docs/template/public/main.js @@ -0,0 +1,14 @@ +/** + * Licensed to the .NET Foundation under one or more agreements. + * The .NET Foundation licenses this file to you under the MIT license. + */ + +export default { + iconLinks: [ + { + icon: 'github', + href: 'https://github.com/dotnet/docfx', + title: 'GitHub' + } + ] +} diff --git a/docs/template/schemas/Dashboard.schema.json b/docs/template/schemas/Dashboard.schema.json new file mode 100644 index 0000000..a00ab75 --- /dev/null +++ b/docs/template/schemas/Dashboard.schema.json @@ -0,0 +1,45 @@ +{ + "title": "Dashboard", + "$schema": "https://dotnet.github.io/docfx/schemas/v1.0/schema.json#", + "version": "1.0.0", + "description": "Schema for dashboard", + "id": "https://github.com/dotnet/docfx/schemas/Dashboard.schema.json", + "type": "object", + "properties": { + "uid": { + "type": "string", + "contentType": "uid" + }, + "title": { + "type": "string", + "tags": [ + "localizable" + ] + }, + "description": { + "type": "string", + "contentType": "markdown", + "tags": [ + "localizable" + ] + }, + "items": { + "items": { + "properties": { + "name": { + "type": "string" + }, + "description": { + "type": "string", + "contentType": "markdown", + "tags": [ + "localizable" + ] + } + }, + "type": "object" + }, + "type": "array" + } + } +} \ No newline at end of file diff --git a/docs/toc.yml b/docs/toc.yml new file mode 100644 index 0000000..1b4ceba --- /dev/null +++ b/docs/toc.yml @@ -0,0 +1,8 @@ +- name: Home + href: index.md +- name: API Documentation + href: api/WebExpress.WebCore.html +- name: User Guide + href: user-guide.md +- name: Tutorials + href: tutorials.md diff --git a/docs/tutorials.md b/docs/tutorials.md new file mode 100644 index 0000000..77c2af0 --- /dev/null +++ b/docs/tutorials.md @@ -0,0 +1,17 @@ +![WebExpress](https://raw.githubusercontent.com/ReneSchwarzer/WebExpress/main/assets/banner.png) + +# Tutorials +Welcome to the `WebExpress` Tutorials! Here, you'll find step-by-step guides and helpful resources to get the most out +of `WebExpress`. Whether you're a beginner just starting out or an experienced developer looking to expand your skills, +our tutorials offer something for everyone. + +# Getting Started +Begin with our basic tutorial: +- [HelloWorld](https://github.com/ReneSchwarzer/WebExpress.Tutorial.HelloWorld#readme) +- [WebApp](https://github.com/ReneSchwarzer/WebExpress.Tutorial.WebApp#readme) +- [WebIndex](https://github.com/ReneSchwarzer/WebExpress.Tutorial.WebIndex#readme) + +This tutorial will guide you through the initial steps of creating and running your first `WebExpress` application. + +Stay tuned for more exciting and educational tutorials to help you unlock the full potential of `WebExpress`. Happy coding and +best of luck with your projects! diff --git a/docs/user-guide.md b/docs/user-guide.md new file mode 100644 index 0000000..3e68bfb --- /dev/null +++ b/docs/user-guide.md @@ -0,0 +1,17 @@ +![WebExpress](https://raw.githubusercontent.com/ReneSchwarzer/WebExpress/main/assets/banner.png) + +# User guide +Welcome to the `WebExpress.WebCore` User Guide. This guide will help you get started with `WebExpress.WebCore` and make the most out of its +features. Follow the links below to begin your journey. + +# Getting started +To get started with `WebExpress.WebCore`, use the following guides: + +- [Installation Guide](https://github.com/ReneSchwarzer/WebExpress/blob/main/doc/installation_guide.md) +- [Development Guide](https://github.com/ReneSchwarzer/WebExpress/blob/main/doc/development_guide.md) +- [WebExpress.WebCore API Documentation](https://reneschwarzer.github.io/WebExpress.WebCore/) +- [WebExpress.WebUI API Documentation](https://reneschwarzer.github.io/WebExpress.WebUI/) +- [WebExpress.WebApp API Documentation](https://reneschwarzer.github.io/WebExpress.WebApp/) +- [WebExpress.WebIndex API Documentation](https://reneschwarzer.github.io/WebExpress.WebIndex/) + +We hope you enjoy using `WebExpress.WebCore` and find it valuable for your projects. Happy coding! diff --git a/src/WebExpress.WebCore.Test/Assets/css/mycss.css b/src/WebExpress.WebCore.Test/Assets/css/mycss.css new file mode 100644 index 0000000..d0446e5 --- /dev/null +++ b/src/WebExpress.WebCore.Test/Assets/css/mycss.css @@ -0,0 +1,14 @@ +/* This CSS file styles the "Hello World" text */ + +/* Style for the body */ +body { + background-color: #f0f0f0; /* Light grey background for the entire page */ + font-family: Arial, sans-serif; /* Set font to Arial */ +} + +/* Style for the heading */ +h1 { + color: #333; /* Dark grey color for the text */ + text-align: center; /* Center the text */ + margin-top: 20%; /* Add space at the top */ +} diff --git a/src/WebExpress.WebCore.Test/Assets/js/myjavascript.js b/src/WebExpress.WebCore.Test/Assets/js/myjavascript.js new file mode 100644 index 0000000..2f61063 --- /dev/null +++ b/src/WebExpress.WebCore.Test/Assets/js/myjavascript.js @@ -0,0 +1,9 @@ +// This script prints "Hello World" to the browser console + +// Define a function that prints "Hello World" +function sayHello() { + console.log("Hello World"); // This prints "Hello World" to the console +} + +// Call the function to print "Hello World" +sayHello(); \ No newline at end of file diff --git a/src/WebExpress.WebCore.Test/Assets/js/myjavascript.mini.js b/src/WebExpress.WebCore.Test/Assets/js/myjavascript.mini.js new file mode 100644 index 0000000..ec1bbc5 --- /dev/null +++ b/src/WebExpress.WebCore.Test/Assets/js/myjavascript.mini.js @@ -0,0 +1 @@ +function sayHello(){console.log("Hello World");}sayHello(); \ No newline at end of file diff --git a/src/WebExpress.WebCore.Test/Data/MockIdentity.cs b/src/WebExpress.WebCore.Test/Data/MockIdentity.cs new file mode 100644 index 0000000..decee36 --- /dev/null +++ b/src/WebExpress.WebCore.Test/Data/MockIdentity.cs @@ -0,0 +1,61 @@ +using WebExpress.WebCore.WebIdentity; + +namespace WebExpress.WebCore.Test.Data +{ + /// + /// Represents an identity object that implements the IIdentity interface. + /// + internal class MockIdentity : IIdentity + { + private readonly List _groups = new(); + + /// + /// Returns or sets the id of the user. + /// + public Guid Id { get; } + + /// + /// Returns or sets the name of the user. + /// + public string Name { get; } + + /// + /// Returns or sets the email of the user. + /// + public string Email { get; } + + /// + /// Returns the hash of the password. + /// + public string PasswordHash { get; } + + /// + /// Returns the groups associated with the user. + /// + public IEnumerable Groups => _groups; + + /// + /// Initializes a new instance of the class with the specified id, name, email, and password. + /// + /// The id of the user. + /// The name of the user. + /// The email of the user. + /// The password hash of the user. + public MockIdentity(Guid id, string name, string email, string passwordHash) + { + Id = id; + Name = name; + Email = email; + PasswordHash = passwordHash; + } + + /// + /// Assigns groups. + /// + /// The list of groups to assign users to. + public void Assign(IEnumerable groups) + { + _groups.AddRange(groups); + } + } +} diff --git a/src/WebExpress.WebCore.Test/Data/MockIdentityFactory.cs b/src/WebExpress.WebCore.Test/Data/MockIdentityFactory.cs new file mode 100644 index 0000000..cd2b9ee --- /dev/null +++ b/src/WebExpress.WebCore.Test/Data/MockIdentityFactory.cs @@ -0,0 +1,119 @@ +using System.Security; +using WebExpress.WebCore.WebIdentity; + +namespace WebExpress.WebCore.Test.Data +{ + /// + /// A factory class for creating mock identities for test users and groups. + /// + internal class MockIdentityFactory + { + private static readonly IEnumerable _groups = CreateTestGroups(); + private static readonly IEnumerable _users = CreateTestUsers(); + + /// + /// Retrieves a mock identity based on the provided name. + /// + /// The name of the identity to retrieve. + /// The mock identity with the specified name, or null if not found. + public static IIdentity GetIdentity(string name) + { + return _users.FirstOrDefault(x => x.Name == name); + } + + /// + /// Retrieves a mock identitygroup based on the provided name. + /// + /// The name of the identity group to retrieve. + /// The mock identity group with the specified name, or null if not found. + public static IIdentityGroup GetIdentityGroup(string name) + { + return _groups.FirstOrDefault(x => x.Name == name); + } + + /// + /// Creates a list of test groups with mock identities. + /// + /// A list of identity groups. + private static IEnumerable CreateTestGroups() + { + var group1 = new MockIdentityGroup(id: Guid.NewGuid(), name: "Admins"); + group1.Assign(["webexpress.webcore.test.testidentityrolea", "webexpress.webcore.test.testidentityroleb"]); + + yield return group1; + + var group2 = new MockIdentityGroup(id: Guid.NewGuid(), name: "Users"); + group2.Assign(["webexpress.webcore.test.testidentityroleb"]); + + yield return group2; + + var group3 = new MockIdentityGroup(id: Guid.NewGuid(), name: "Guests"); + group3.Assign([]); + + yield return group3; + } + + /// + /// Creates a list of test users with mock identities. + /// + /// A list of identities. + private static IEnumerable CreateTestUsers() + { + var password = new SecureString(); + password.AppendChar('a'); + password.AppendChar('b'); + password.AppendChar('c'); + password.MakeReadOnly(); + + var user = new MockIdentity(Guid.NewGuid(), "Alice", "alice@example.com", IdentityManager.ComputeHash(password)); + user.Assign([_groups.ElementAt(0)]); + + yield return user; + + user = new MockIdentity(Guid.NewGuid(), "Bob", "bob@example.com", IdentityManager.ComputeHash(password)); + user.Assign([_groups.ElementAt(1)]); + + yield return user; + + user = new MockIdentity(Guid.NewGuid(), "Charlie", "charlie@example.com", IdentityManager.ComputeHash(password)); + user.Assign([_groups.ElementAt(2)]); + + yield return user; + + user = new MockIdentity(Guid.NewGuid(), "David", "david@example.com", IdentityManager.ComputeHash(password)); + user.Assign([_groups.ElementAt(1), _groups.ElementAt(2)]); + + yield return user; + + user = new MockIdentity(Guid.NewGuid(), "Eve", "eve@example.com", IdentityManager.ComputeHash(password)); + user.Assign([_groups.ElementAt(1)]); + + yield return user; + + user = new MockIdentity(Guid.NewGuid(), "Frank", "frank@example.com", IdentityManager.ComputeHash(password)); + user.Assign([_groups.ElementAt(1)]); + + yield return user; + + user = new MockIdentity(Guid.NewGuid(), "Grace", "grace@example.com", IdentityManager.ComputeHash(password)); + user.Assign([_groups.ElementAt(1)]); + + yield return user; + + user = new MockIdentity(Guid.NewGuid(), "Heidi", "heidi@example.com", IdentityManager.ComputeHash(password)); + user.Assign([_groups.ElementAt(1)]); + + yield return user; + + user = new MockIdentity(Guid.NewGuid(), "Ivan", "ivan@example.com", IdentityManager.ComputeHash(password)); + user.Assign([_groups.ElementAt(1)]); + + yield return user; + + user = new MockIdentity(Guid.NewGuid(), "Judy", "judy@example.com", IdentityManager.ComputeHash(password)); + user.Assign([_groups.ElementAt(1)]); + + yield return user; + } + } +} diff --git a/src/WebExpress.WebCore.Test/Data/MockIdentityGroup.cs b/src/WebExpress.WebCore.Test/Data/MockIdentityGroup.cs new file mode 100644 index 0000000..e566770 --- /dev/null +++ b/src/WebExpress.WebCore.Test/Data/MockIdentityGroup.cs @@ -0,0 +1,47 @@ +using WebExpress.WebCore.WebIdentity; + +namespace WebExpress.WebCore.Test.Data +{ + /// + /// Represents a group identity with an ID and a name. + /// + internal class MockIdentityGroup : IIdentityGroup + { + private readonly List _roles = []; + + /// + /// Returns or sets the id of the group. + /// + public Guid Id { get; set; } + + /// + /// Returns or sets the name of the group. + /// + public string Name { get; set; } + + /// + /// Returns the roles associated with the group. + /// + public IEnumerable Roles => _roles; + + /// + /// Initializes a new instance of the class with the specified id and name. + /// + /// The id of the group. + /// The name of the group. + public MockIdentityGroup(Guid id, string name) + { + Id = id; + Name = name; + } + + /// + /// Assigns roles. + /// + /// The list of roles to assign group to. + public void Assign(IEnumerable roles) + { + _roles.AddRange(roles); + } + } +} diff --git a/src/WebExpress.WebCore.Test/Fixture/NonParallelTestsCollection.cs b/src/WebExpress.WebCore.Test/Fixture/NonParallelTestsCollection.cs new file mode 100644 index 0000000..1db3664 --- /dev/null +++ b/src/WebExpress.WebCore.Test/Fixture/NonParallelTestsCollection.cs @@ -0,0 +1,11 @@ +namespace WebExpress.WebCore.Test.Fixture +{ + /// + /// Defines a collection of tests that should not be run in parallel. + /// + [CollectionDefinition("NonParallelTests", DisableParallelization = true)] + public class NonParallelTestsCollection : ICollectionFixture + { + + } +} diff --git a/src/WebExpress.WebCore.Test/Fixture/UnitTestFixture.cs b/src/WebExpress.WebCore.Test/Fixture/UnitTestFixture.cs new file mode 100644 index 0000000..31d7728 --- /dev/null +++ b/src/WebExpress.WebCore.Test/Fixture/UnitTestFixture.cs @@ -0,0 +1,239 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Features; +using System.Globalization; +using System.Net; +using System.Reflection; +using System.Text; +using WebExpress.WebCore.WebApplication; +using WebExpress.WebCore.WebComponent; +using WebExpress.WebCore.WebEndpoint; +using WebExpress.WebCore.WebLog; +using WebExpress.WebCore.WebMessage; +using WebExpress.WebCore.WebPage; +using WebExpress.WebCore.WebPlugin; +using WebExpress.WebCore.WebUri; + +namespace WebExpress.WebCore.Test.Fixture +{ + /// + /// A fixture class for unit tests, providing various mock objects and utility methods. + /// + public class UnitTestFixture : IDisposable + { + private static readonly string[] _separator = ["\r\n", "\r", "\n"]; + + /// + /// Initializes a new instance of the class and boot the component manager. + /// + public UnitTestFixture() + { + } + + /// + /// Create a fake server context. + /// + /// The server context. + public static IHttpServerContext CreateHttpServerContextMock() + { + return new HttpServerContext + ( + new RouteEndpoint("localhost"), + [], + "", + Environment.CurrentDirectory, + Environment.CurrentDirectory, + Environment.CurrentDirectory, + new RouteEndpoint("/server"), + CultureInfo.GetCultureInfo("en"), + new Log() { LogMode = LogMode.Off }, + null + ); + } + + /// + /// Create a component hub. + /// + /// The component hub. + public static ComponentHub CreateComponentHubMock() + { + var ctorComponentHub = typeof(ComponentHub).GetConstructor + ( + BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, + null, + [typeof(HttpServerContext)], + null + ); + + var componentHub = (ComponentHub)ctorComponentHub.Invoke([CreateHttpServerContextMock()]); + + // set static field in the webex class + var type = typeof(WebEx); + var field = type.GetField("_componentHub", BindingFlags.Static | BindingFlags.NonPublic); + + field.SetValue(null, componentHub); + + return componentHub; + } + + /// + /// Create a component hub and register the plugins. + /// + /// The component hub. + public static ComponentHub CreateAndRegisterComponentHubMock() + { + var componentHub = CreateComponentHubMock(); + var pluginManager = componentHub.PluginManager as PluginManager; + + pluginManager.Register(); + + return componentHub; + } + + /// + /// Create a fake request. + /// + /// The content of the request. + /// The URI of the request. + /// A fake request for testing. + public static Request CrerateRequestMock(string content = "", string uri = "") + { + var context = CreateHttpContextMock(content); + + var request = context.Request; + + if (!string.IsNullOrEmpty(uri)) + { + request.Uri = new UriEndpoint(uri); + } + + return request; + } + + /// + /// Create a fake http context. + /// + /// The content. + /// A fake http context for testing. + public static WebMessage.HttpContext CreateHttpContextMock(string content = "") + { + var ctorRequest = typeof(Request).GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null, [typeof(IFeatureCollection), typeof(RequestHeaderFields), typeof(IHttpServerContext)], null); + var featureCollection = new FeatureCollection(); + var firstLine = content.Split('\n').FirstOrDefault(); + var lines = content.Split(_separator, StringSplitOptions.None); + var filteredLines = lines.Skip(1).TakeWhile(line => !string.IsNullOrWhiteSpace(line)); + var pos = content.Length > 0 ? content.IndexOf(filteredLines.LastOrDefault()) + filteredLines.LastOrDefault().Length + 4 : 0; + var innerContent = pos < content.Length ? content[pos..] : ""; + var contentBytes = Encoding.UTF8.GetBytes(innerContent); + + var requestFeature = new HttpRequestFeature + { + Headers = new HeaderDictionary + { + ["Host"] = "localhost", + ["Connection"] = "keep-alive", + ["ContentType"] = "text/html", + ["ContentLength"] = innerContent.Length.ToString(), + ["ContentLanguage"] = "en", + ["ContentEncoding"] = "gzip, deflate, br, zstd", + ["Accept"] = "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", + ["AcceptEncoding"] = "gzip, deflate, br, zstd", + ["AcceptLanguage"] = "de,de-DE;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6", + ["UserAgent"] = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36 Edg/126.0.0.0", + ["Referer"] = "0HN50661TV8TP", + ["Cookie"] = "session=AB333C76-E73F-45E0-85FD-123320D9B85F" + }, + Body = contentBytes.Length > 0 ? new MemoryStream(contentBytes) : null, + Method = firstLine.Split(' ')?.Where(x => !string.IsNullOrEmpty(x)).FirstOrDefault() ?? "GET", + RawTarget = firstLine.Split(' ')?.Skip(1)?.FirstOrDefault()?.Split('?')?.FirstOrDefault() ?? "/", + QueryString = "?" + firstLine.Split(' ')?.Skip(1)?.FirstOrDefault()?.Split('?')?.Skip(1)?.FirstOrDefault() ?? "", + }; + + foreach (var line in filteredLines) + { + var key = line.Split(':').FirstOrDefault().Trim(); + var value = line.Split(':').Skip(1).FirstOrDefault().Trim(); + requestFeature.Headers[key] = value; + } + + requestFeature.Headers.ContentLength = contentBytes.Length; + + var requestIdentifierFeature = new HttpRequestIdentifierFeature + { + TraceIdentifier = "Ihr TraceIdentifier-Wert" + }; + + var connectionFeature = new HttpConnectionFeature + { + LocalPort = 8080, + LocalIpAddress = IPAddress.Parse("192.168.0.1"), + RemotePort = 8080, + RemoteIpAddress = IPAddress.Parse("127.0.0.1"), + ConnectionId = "0HN50661TV8TP" + }; + + featureCollection.Set(requestFeature); + featureCollection.Set(requestIdentifierFeature); + featureCollection.Set(connectionFeature); + + var context = new WebMessage.HttpContext(featureCollection, CreateHttpServerContextMock()); + + return context; + } + + /// + /// Creates a mock render context for unit testing. + /// + /// The application context. If null, defaults to null. + /// The scopes of the page. If null, defaults to null. + /// A mock render context for testing. + public static RenderContext CrerateRenderContextMock(IApplicationContext applicationContext = null, IEnumerable scopes = null) + { + var request = CrerateRequestMock(); + + return new RenderContext(null, CreratePageContextMock(applicationContext, scopes), request); + } + + /// + /// Create a fake page context for unit testing. + /// + /// The scopes of the page. + /// A fake context for testing. + public static PageContext CreratePageContextMock(IApplicationContext applicationContext = null, IEnumerable scopes = null) + { + var ctorPageContext = typeof(PageContext).GetConstructor(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, [], null); + + var pageContext = (PageContext)ctorPageContext.Invoke([]); + pageContext.ApplicationContext = applicationContext; + pageContext.Scopes = scopes; + + return pageContext; + } + + /// + /// Gets the content of an embedded resource as a string. + /// + /// The name of the resource file. + /// The content of the embedded resource as a string. + public static string GetEmbeddedResource(string fileName) + { + var assembly = typeof(UnitTestFixture).Assembly; + var resourceName = assembly.GetManifestResourceNames() + .FirstOrDefault(name => name.EndsWith(fileName, StringComparison.OrdinalIgnoreCase)); + + using var stream = assembly.GetManifestResourceStream(resourceName); + using var memoryStream = new MemoryStream(); + stream.CopyTo(memoryStream); + var data = memoryStream.ToArray(); + + return Encoding.UTF8.GetString(data); + } + + /// + /// Release of unmanaged resources reserved during use. + /// + public void Dispose() + { + GC.SuppressFinalize(this); + } + } +} diff --git a/src/WebExpress.WebCore.Test/Html/UnitTestHtmlElementFieldLabel.cs b/src/WebExpress.WebCore.Test/Html/UnitTestHtmlElementFieldLabel.cs new file mode 100644 index 0000000..cd716f1 --- /dev/null +++ b/src/WebExpress.WebCore.Test/Html/UnitTestHtmlElementFieldLabel.cs @@ -0,0 +1,131 @@ +using WebExpress.WebCore.WebHtml; + +namespace WebExpress.WebCore.Test.Html +{ + /// + /// Unit tests for the HtmlElementFieldLabel class. + /// + [Collection("NonParallelTests")] + public class UnitTestHtmlElementFieldLabel + { + /// + /// Tests a empty tag. + /// + [Fact] + public void Empty() + { + // test execution + var html = new HtmlElementFieldLabel(); + + Assert.Equal(@"", html.Trim()); + } + + /// + /// Tests a tag. + /// + [Fact] + public void TextAtInstancing() + { + // test execution + var html = new HtmlElementFieldLabel("abcdef"); + + Assert.Equal(@"", html.Trim()); + } + + /// + /// Tests a tag. + /// + [Fact] + public void TextAtProperty() + { + // test execution + var html = new HtmlElementFieldLabel + { + Text = "abcdef" + }; + + Assert.Equal(@"", html.Trim()); + } + + /// + /// Tests a tag. + /// + [Fact] + public void TextAtHtmlText() + { + // test execution + var html = new HtmlElementFieldLabel(new HtmlText("abc"), new HtmlText("def")); + + Assert.Equal(@"", html.Trim()); + } + + /// + /// Tests a tag. + /// + [Fact] + public void TextWithId() + { + // test execution + var html = new HtmlElementFieldLabel() + { + Id = "identity" + }; + + Assert.Equal(@"", html.Trim()); + } + + /// + /// Tests a tag. + /// + [Fact] + public void Inline() + { + // test execution + var html = new HtmlElementFieldLabel(); + + Assert.False(html.Inline); + } + + /// + /// Tests a tag. + /// + [Fact] + public void CloseTag() + { + // test execution + var html = new HtmlElementFieldLabel(); + + Assert.True(html.CloseTag); + } + + /// + /// Tests the class attribute. + /// + [Fact] + public void Class() + { + // test execution + var html = new HtmlElementFieldLabel() + { + Class = "abc" + }; + + Assert.Equal(@"", html.Trim()); + } + + /// + /// Tests the style attribute. + /// + [Fact] + public void Style() + { + // test execution + var html = new HtmlElementFieldLabel() + { + Style = "abc" + }; + + Assert.Equal(@"", html.Trim()); + } + } +} diff --git a/src/WebExpress.WebCore.Test/Html/UnitTestHtmlElementTextContentP.cs b/src/WebExpress.WebCore.Test/Html/UnitTestHtmlElementTextContentP.cs new file mode 100644 index 0000000..555fdfa --- /dev/null +++ b/src/WebExpress.WebCore.Test/Html/UnitTestHtmlElementTextContentP.cs @@ -0,0 +1,132 @@ +using WebExpress.WebCore.WebHtml; + +namespace WebExpress.WebCore.Test.Html +{ + /// + /// Unit tests for the HtmlElementTextContentP class. + /// + [Collection("NonParallelTests")] + public class UnitTestHtmlElementTextContentP + { + /// + /// Tests a empty tag. + /// + [Fact] + public void Empty() + { + // test execution + var html = new HtmlElementTextContentP(); + + Assert.Equal(@"

", html.Trim()); + } + + /// + /// Tests a tag. + /// + [Fact] + public void TextAtInstancing() + { + // test execution + var html = new HtmlElementTextContentP("abcdef"); + + Assert.Equal(@"

abcdef

", html.Trim()); + } + + /// + /// Tests a tag. + /// + [Fact] + public void TextAtProperty() + { + // test execution + var html = new HtmlElementTextContentP + { + Text = "abcdef" + }; + + Assert.Equal(@"

abcdef

", html.Trim()); + } + + /// + /// Tests a tag. + /// + [Fact] + public void TextAtHtmlText() + { + // test execution + var html = new HtmlElementTextContentP(new HtmlText("abc"), new HtmlText("def")); + var str = html.ToString(); + + Assert.Equal(@"

abcdef

", html.Trim()); + } + + /// + /// Tests a tag. + /// + [Fact] + public void TextWithId() + { + // test execution + var html = new HtmlElementTextContentP() + { + Id = "identity" + }; + + Assert.Equal(@"

", html.Trim()); + } + + /// + /// Tests a tag. + /// + [Fact] + public void Inline() + { + // test execution + var html = new HtmlElementTextContentP(); + + Assert.False(html.Inline); + } + + /// + /// Tests a tag. + /// + [Fact] + public void CloseTag() + { + // test execution + var html = new HtmlElementTextContentP(); + + Assert.True(html.CloseTag); + } + + /// + /// Tests a tag. + /// + [Fact] + public void Class() + { + // test execution + var html = new HtmlElementTextContentP() + { + Class = "abc" + }; + + Assert.Equal(@"

", html.Trim()); + } + + /// + /// Tests a tag. + /// + [Fact] + public void Style() + { + // test execution + var html = new HtmlElementTextContentP() + { + Style = "abc" + }; + + Assert.Equal(@"

", html.Trim()); + } + } +} diff --git a/src/WebExpress.WebCore.Test/Html/UnitTestHtmlImage.cs b/src/WebExpress.WebCore.Test/Html/UnitTestHtmlImage.cs new file mode 100644 index 0000000..f5c9d85 --- /dev/null +++ b/src/WebExpress.WebCore.Test/Html/UnitTestHtmlImage.cs @@ -0,0 +1,23 @@ +using WebExpress.WebCore.WebHtml; + +namespace WebExpress.WebCore.Test.Html +{ + /// + /// Unit tests for the HtmlElementMultimediaImg class. + /// + [Collection("NonParallelTests")] + public class UnitTestHtmlImage + { + /// + /// Tests a empty image. + /// + [Fact] + public void Empty() + { + // test execution + var html = new HtmlElementMultimediaImg(); + + Assert.Equal(@"", html.ToString().Trim()); + } + } +} diff --git a/src/WebExpress.WebCore.Test/Html/UnitTestHtmlText.cs b/src/WebExpress.WebCore.Test/Html/UnitTestHtmlText.cs new file mode 100644 index 0000000..4d499af --- /dev/null +++ b/src/WebExpress.WebCore.Test/Html/UnitTestHtmlText.cs @@ -0,0 +1,50 @@ +using WebExpress.WebCore.WebHtml; + +namespace WebExpress.WebCore.Test.Html +{ + /// + /// Unit tests for the HtmlText class. + /// + [Collection("NonParallelTests")] + public class UnitTestHtmlText + { + /// + /// Tests a empty tag. + /// + [Fact] + public void Empty() + { + // test execution + var html = new HtmlText(); + + Assert.Null(html.Value); + } + + /// + /// Tests a tag. + /// + [Fact] + public void TextAtInstancing() + { + // test execution + var html = new HtmlText("abcdef"); + + Assert.Equal(@"abcdef", html.Value); + } + + /// + /// Tests a tag. + /// + [Fact] + public void TextAtProperty() + { + // test execution + var html = new HtmlText + { + Value = "abcdef" + }; + + Assert.Equal(@"abcdef", html.Value); + } + } +} diff --git a/src/WebExpress.WebCore.Test/IHtmlNodeExtensions.cs b/src/WebExpress.WebCore.Test/IHtmlNodeExtensions.cs new file mode 100644 index 0000000..3da4116 --- /dev/null +++ b/src/WebExpress.WebCore.Test/IHtmlNodeExtensions.cs @@ -0,0 +1,24 @@ +using System.Text.RegularExpressions; +using WebExpress.WebCore.WebHtml; + +namespace WebExpress.WebCore.Test +{ + internal static class IHtmlNodeExtensions + { + /// + /// Removes extra spaces and line breaks from the input node. + /// + /// The node to clean. + /// A string with all consecutive spaces and line breaks reduced to single ones. + public static string Trim(this IHtmlNode node) + { + var withoutExtraLineBreaks = Regex.Replace(node.ToString().Trim(), @"[\r\n]+", ""); + var withoutExtraSpaces = Regex.Replace(withoutExtraLineBreaks, @"\s+", " "); + var withoutSpacesBetweenBrackets = Regex.Replace(withoutExtraSpaces, @">\s+<", "><"); + var withoutSpacesLeftBrackets = Regex.Replace(withoutSpacesBetweenBrackets, @"\s+<", "<"); + var withoutSpacesRightBrackets = Regex.Replace(withoutSpacesLeftBrackets, @">\s+", ">"); + + return withoutSpacesRightBrackets; + } + } +} diff --git a/src/WebExpress.WebCore.Test/Internationalization/de b/src/WebExpress.WebCore.Test/Internationalization/de new file mode 100644 index 0000000..5d7a4b0 --- /dev/null +++ b/src/WebExpress.WebCore.Test/Internationalization/de @@ -0,0 +1,5 @@ +# Deutsch + +unit.test.message=Dies ist ein Test +welcome.message=Willkommen '{0}' zu unserer Anwendung! +logout.button=Abmelden diff --git a/src/WebExpress.WebCore.Test/Internationalization/en b/src/WebExpress.WebCore.Test/Internationalization/en new file mode 100644 index 0000000..902b8d4 --- /dev/null +++ b/src/WebExpress.WebCore.Test/Internationalization/en @@ -0,0 +1,5 @@ +# English + +unit.test.message=This is a test +welcome.message=Welcome '{0}' to our application! +logout.button=Logout \ No newline at end of file diff --git a/src/WebExpress.WebCore.Test/Manager/UnitTestApplication.cs b/src/WebExpress.WebCore.Test/Manager/UnitTestApplication.cs new file mode 100644 index 0000000..59c4530 --- /dev/null +++ b/src/WebExpress.WebCore.Test/Manager/UnitTestApplication.cs @@ -0,0 +1,198 @@ +using WebExpress.WebCore.Test.Fixture; +using WebExpress.WebCore.WebApplication; +using WebExpress.WebCore.WebComponent; +using WebExpress.WebCore.WebPlugin; + +namespace WebExpress.WebCore.Test.Manager +{ + /// + /// Test the application manager. + /// + [Collection("NonParallelTests")] + public class UnitTestApplication + { + /// + /// Test the register function of the application manager. + /// + [Fact] + public void Register() + { + // preconditions + var componentHub = UnitTestFixture.CreateComponentHubMock(); + var pluginManager = componentHub.PluginManager as PluginManager; + + // test execution + pluginManager.Register(); + + Assert.Equal(3, componentHub.ApplicationManager.Applications.Count()); + Assert.Equal("webexpress.webcore.test.testapplicationa", componentHub.ApplicationManager.GetApplications(typeof(TestApplicationA)).FirstOrDefault()?.ApplicationId); + Assert.Equal("webexpress.webcore.test.testapplicationb", componentHub.ApplicationManager.GetApplications(typeof(TestApplicationB)).FirstOrDefault()?.ApplicationId); + Assert.Equal("webexpress.webcore.test.testapplicationc", componentHub.ApplicationManager.GetApplications(typeof(TestApplicationC)).FirstOrDefault()?.ApplicationId); + } + + /// + /// Test the remove function of the application manager. + /// + [Fact] + public void Remove() + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + var applicationManager = componentHub.ApplicationManager as ApplicationManager; + var plugin = componentHub.PluginManager.GetPlugin(typeof(TestPlugin)); + + // test execution + applicationManager.Remove(plugin); + + Assert.Empty(componentHub.ApplicationManager.Applications); + } + + /// + /// Test the name property of the application. + /// + [Theory] + [InlineData(typeof(TestApplicationA), "webexpress.webcore.test.testapplicationa")] + [InlineData(typeof(TestApplicationB), "webexpress.webcore.test.testapplicationb")] + [InlineData(typeof(TestApplicationC), "webexpress.webcore.test.testapplicationc")] + public void Id(Type applicationType, string id) + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + var application = componentHub.ApplicationManager.GetApplications(applicationType).FirstOrDefault(); + + // test execution + Assert.Equal(id, application.ApplicationId); + } + + /// + /// Test the name property of the application. + /// + [Theory] + [InlineData(typeof(TestApplicationA), "TestApplicationA")] + [InlineData(typeof(TestApplicationB), "TestApplicationB")] + [InlineData(typeof(TestApplicationC), "TestApplicationC")] + public void Name(Type applicationType, string name) + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + var application = componentHub.ApplicationManager.GetApplications(applicationType).FirstOrDefault(); + + // test execution + Assert.Equal(name, application.ApplicationName); + } + + /// + /// Test the description property of the application. + /// + [Theory] + [InlineData(typeof(TestApplicationA), "application.description")] + [InlineData(typeof(TestApplicationB), "application.description")] + [InlineData(typeof(TestApplicationC), "application.description")] + public void Description(Type applicationType, string description) + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + var application = componentHub.ApplicationManager.GetApplications(applicationType).FirstOrDefault(); + + // test execution + Assert.Equal(description, application.Description); + } + + /// + /// Test the icon property of the application. + /// + [Theory] + [InlineData(typeof(TestApplicationA), "/server/appa/assets/img/Logo.png")] + [InlineData(typeof(TestApplicationB), "/server/appb/assets/img/Logo.png")] + [InlineData(typeof(TestApplicationC), "/server/assets/img/Logo.png")] + public void Icon(Type applicationType, string icon) + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + var application = componentHub.ApplicationManager.GetApplications(applicationType).FirstOrDefault(); + + // test execution + Assert.Equal(icon, application.Icon.ToString()); + } + + /// + /// Test the context path property of the application. + /// + [Theory] + [InlineData(typeof(TestApplicationA), "/server/appa")] + [InlineData(typeof(TestApplicationB), "/server/appb")] + [InlineData(typeof(TestApplicationC), "/server")] + public void ContextPath(Type applicationType, string contextPath) + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + var application = componentHub.ApplicationManager.GetApplications(applicationType).FirstOrDefault(); + + // test execution + Assert.Equal(contextPath, application.ContextPath.ToString()); + } + + /// + /// Test the asset path property of the application. + /// + [Theory] + [InlineData(typeof(TestApplicationA), "/asseta")] + [InlineData(typeof(TestApplicationB), "/assetb")] + [InlineData(typeof(TestApplicationC), "/")] + public void AssetPath(Type applicationType, string assetPath) + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + var application = componentHub.ApplicationManager.GetApplications(applicationType).FirstOrDefault(); + + // test execution + Assert.Equal(assetPath, application.AssetPath); + } + + /// + /// Test the data path property of the application. + /// + [Theory] + [InlineData(typeof(TestApplicationA), "/dataa")] + [InlineData(typeof(TestApplicationB), "/datab")] + [InlineData(typeof(TestApplicationC), "/")] + public void DataPath(Type applicationType, string dataPath) + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + var application = componentHub.ApplicationManager.GetApplications(applicationType).FirstOrDefault(); + + // test execution + Assert.Equal(dataPath, application.DataPath); + } + + /// + /// Tests whether the application manager implements interface IComponentManager. + /// + [Fact] + public void IsIComponentManager() + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + + // test execution + Assert.True(typeof(IComponentManager).IsAssignableFrom(componentHub.ApplicationManager.GetType())); + } + + /// + /// Tests whether the application context implements interface IContext. + /// + [Fact] + public void IsIContext() + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + + // test execution + foreach (var application in componentHub.ApplicationManager.Applications) + { + Assert.True(typeof(IContext).IsAssignableFrom(application.GetType()), $"Application context {application.GetType().Name} does not implement IContext."); + } + } + } +} diff --git a/src/WebExpress.WebCore.Test/Manager/UnitTestAssetManager.cs b/src/WebExpress.WebCore.Test/Manager/UnitTestAssetManager.cs new file mode 100644 index 0000000..9b39472 --- /dev/null +++ b/src/WebExpress.WebCore.Test/Manager/UnitTestAssetManager.cs @@ -0,0 +1,169 @@ +using System.Text; +using WebExpress.WebCore.Test.Fixture; +using WebExpress.WebCore.WebAsset; +using WebExpress.WebCore.WebComponent; +using WebExpress.WebCore.WebMessage; +using WebExpress.WebCore.WebSitemap; + +namespace WebExpress.WebCore.Test.Manager +{ + /// + /// Test the asset manager. + /// + [Collection("NonParallelTests")] + public class UnitTestAssetManager + { + /// + /// Test the register function of the asset manager. + /// + [Fact] + public void Register() + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + + // test execution + Assert.Equal(9, componentHub.AssetManager.Assets.Count()); + } + + /// + /// Test the remove function of the asset manager. + /// + [Fact] + public void Remove() + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + var plugin = componentHub.PluginManager.GetPlugin(typeof(TestPlugin)); + var resourceManager = componentHub.AssetManager as AssetManager; + + // test execution + resourceManager.Remove(plugin); + + Assert.Empty(componentHub.AssetManager.Assets); + } + + /// + /// Test the id property of the asset. + /// + [Theory] + [InlineData(typeof(TestApplicationA), "css.mycss.css")] + [InlineData(typeof(TestApplicationA), "js.myjavascript.js")] + [InlineData(typeof(TestApplicationA), "js.myjavascript.mini.js")] + [InlineData(typeof(TestApplicationB), "css.mycss.css")] + [InlineData(typeof(TestApplicationB), "js.myjavascript.js")] + [InlineData(typeof(TestApplicationB), "js.myjavascript.mini.js")] + [InlineData(typeof(TestApplicationC), "css.mycss.css")] + [InlineData(typeof(TestApplicationC), "js.myjavascript.js")] + [InlineData(typeof(TestApplicationC), "js.myjavascript.mini.js")] + public void Id(Type applicationType, string id) + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + var application = componentHub.ApplicationManager.GetApplications(applicationType)?.FirstOrDefault(); + var asset = componentHub.AssetManager.GetAssets(application)?.FirstOrDefault(x => x.EndpointId.ToString() == id); + + // test execution + Assert.Equal(id, asset?.EndpointId.ToString()); + } + + /// + /// Test the uri property of the asset. + /// + [Theory] + [InlineData(typeof(TestApplicationA), "/server/appa/assets/css.mycss.css")] + [InlineData(typeof(TestApplicationA), "/server/appa/assets/js.myjavascript.js")] + [InlineData(typeof(TestApplicationA), "/server/appa/assets/js.myjavascript.mini.js")] + [InlineData(typeof(TestApplicationB), "/server/appb/assets/css.mycss.css")] + [InlineData(typeof(TestApplicationB), "/server/appb/assets/js.myjavascript.js")] + [InlineData(typeof(TestApplicationB), "/server/appb/assets/js.myjavascript.mini.js")] + [InlineData(typeof(TestApplicationC), "/server/assets/css.mycss.css")] + [InlineData(typeof(TestApplicationC), "/server/assets/js.myjavascript.js")] + [InlineData(typeof(TestApplicationC), "/server/assets/js.myjavascript.mini.js")] + public void Uri(Type applicationType, string uri) + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + var application = componentHub.ApplicationManager.GetApplications(applicationType)?.FirstOrDefault(); + var asset = componentHub.AssetManager.GetAssets(application)?.FirstOrDefault(x => x.EndpointId.ToString() == Path.GetFileName(uri)); + + // test execution + Assert.Equal(uri, asset?.Route.ToString()); + } + + /// + /// Test the request of the asset. + /// + [Theory] + [InlineData("http://localhost:8080/server/appa/assets/css/mycss.css", "webexpress.webcore.asset", "css.mycss.css")] + [InlineData("http://localhost:8080/server/appa/assets/js/myjavascript.js", "webexpress.webcore.asset", "js.myjavascript.js")] + [InlineData("http://localhost:8080/server/appa/assets/js/myjavascript.mini.js", "webexpress.webcore.asset", "js.myjavascript.mini.js")] + [InlineData("http://localhost:8080/server/appa/assets/css.mycss.css", "webexpress.webcore.asset", "css.mycss.css")] + [InlineData("http://localhost:8080/server/appa/assets/js.myjavascript.js", "webexpress.webcore.asset", "js.myjavascript.js")] + [InlineData("http://localhost:8080/server/appa/assets/js.myjavascript.mini.js", "webexpress.webcore.asset", "js.myjavascript.mini.js")] + [InlineData("http://localhost:8080/server/appb/assets/css/mycss.css", "webexpress.webcore.asset", "css.mycss.css")] + [InlineData("http://localhost:8080/server/appb/assets/js/myjavascript.js", "webexpress.webcore.asset", "js.myjavascript.js")] + [InlineData("http://localhost:8080/server/appb/assets/js/myjavascript.mini.js", "webexpress.webcore.asset", "js.myjavascript.mini.js")] + [InlineData("http://localhost:8080/server/appb/assets/css.mycss.css", "webexpress.webcore.asset", "css.mycss.css")] + [InlineData("http://localhost:8080/server/appb/assets/js.myjavascript.js", "webexpress.webcore.asset", "js.myjavascript.js")] + [InlineData("http://localhost:8080/server/appb/assets/js.myjavascript.mini.js", "webexpress.webcore.asset", "js.myjavascript.mini.js")] + [InlineData("http://localhost:8080/server/assets/css/mycss.css", "webexpress.webcore.asset", "css.mycss.css")] + [InlineData("http://localhost:8080/server/assets/js/myjavascript.js", "webexpress.webcore.asset", "js.myjavascript.js")] + [InlineData("http://localhost:8080/server/assets/js/myjavascript.mini.js", "webexpress.webcore.asset", "js.myjavascript.mini.js")] + [InlineData("http://localhost:8080/server/assets/css.mycss.css", "webexpress.webcore.asset", "css.mycss.css")] + [InlineData("http://localhost:8080/server/assets/js.myjavascript.js", "webexpress.webcore.asset", "js.myjavascript.js")] + [InlineData("http://localhost:8080/server/assets/js.myjavascript.mini.js", "webexpress.webcore.asset", "js.myjavascript.mini.js")] + public void Request(string uri, string id, string resource) + { + // preconditions + var embeddedResource = UnitTestFixture.GetEmbeddedResource(resource); + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + var context = UnitTestFixture.CreateHttpContextMock(); + var httpServerContext = UnitTestFixture.CreateHttpServerContextMock(); + componentHub.SitemapManager.Refresh(); + + // test execution + var searchResult = componentHub.SitemapManager.SearchResource(new System.Uri(uri), new SearchContext() + { + HttpServerContext = httpServerContext, + Culture = httpServerContext.Culture, + HttpContext = context + }); + + var response = componentHub.EndpointManager.HandleRequest(UnitTestFixture.CrerateRequestMock("", uri), searchResult.EndpointContext); + + Assert.Equal(id, searchResult?.EndpointContext?.EndpointId.ToString()); + Assert.IsNotType(response); + Assert.Equal(embeddedResource, Encoding.UTF8.GetString(response.Content as byte[])); + } + + /// + /// Tests whether the asset manager implements interface IComponentManager. + /// + [Fact] + public void IsIComponentManager() + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + + // test execution + Assert.True(typeof(IComponentManager).IsAssignableFrom(componentHub.AssetManager.GetType())); + } + + /// + /// Tests whether the asset context implements interface IContext. + /// + [Fact] + public void IsIContext() + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + + // test execution + foreach (var asset in componentHub.AssetManager.Assets) + { + Assert.True(typeof(IContext).IsAssignableFrom(asset.GetType()), $"Asset context {asset.GetType().Name} does not implement IContext."); + } + } + } +} diff --git a/src/WebExpress.WebCore.Test/Manager/UnitTestComponentManager.cs b/src/WebExpress.WebCore.Test/Manager/UnitTestComponentManager.cs new file mode 100644 index 0000000..d0ebaaf --- /dev/null +++ b/src/WebExpress.WebCore.Test/Manager/UnitTestComponentManager.cs @@ -0,0 +1,37 @@ +using WebExpress.WebCore.Test.Fixture; + +namespace WebExpress.WebCore.Test.Manager +{ + /// + /// Test the component manager. + /// + [Collection("NonParallelTests")] + public class UnitTestComponentManager + { + /// + /// Test the plugin manager property of the component manager. + /// + [Fact] + public void PluginManager() + { + // preconditions + var componentHub = UnitTestFixture.CreateComponentHubMock(); + + // test execution + Assert.NotNull(componentHub.PluginManager); + } + + /// + /// Test the application manager property of the component manager. + /// + [Fact] + public void ApplicationManager() + { + // preconditions + var componentHub = UnitTestFixture.CreateComponentHubMock(); + + // test execution + Assert.NotNull(componentHub.ApplicationManager); + } + } +} diff --git a/src/WebExpress.WebCore.Test/Manager/UnitTestEventManager.cs b/src/WebExpress.WebCore.Test/Manager/UnitTestEventManager.cs new file mode 100644 index 0000000..9c68326 --- /dev/null +++ b/src/WebExpress.WebCore.Test/Manager/UnitTestEventManager.cs @@ -0,0 +1,144 @@ +using WebExpress.WebCore.Test.Fixture; +using WebExpress.WebCore.WebComponent; +using WebExpress.WebCore.WebEvent; + +namespace WebExpress.WebCore.Test.Manager +{ + /// + /// Test the event manager. + /// + [Collection("NonParallelTests")] + public class UnitTestEventManager + { + /// + /// Test the register function of the event manager. + /// + [Fact] + public void Register() + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + + // test execution + Assert.Equal(6, componentHub.EventManager.EventHandlers.Count()); + } + + /// + /// Test the remove function of the event manager. + /// + [Fact] + public void Remove() + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + var eventManager = componentHub.EventManager as EventManager; + var plugin = componentHub.PluginManager.GetPlugin(typeof(TestPlugin)); + + // test execution + eventManager.Remove(plugin); + + Assert.Empty(componentHub.EventManager.EventHandlers); + } + + /// + /// Tests whether the event manager implements interface IComponentManager. + /// + [Fact] + public void IsIComponentManager() + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + + // test execution + Assert.True(typeof(IComponentManager).IsAssignableFrom(componentHub.EventManager.GetType())); + } + + /// + /// Test the id property of the event handler. + /// + [Theory] + [InlineData(typeof(TestApplicationA), typeof(TestEventA), "webexpress.webcore.test.testeventhandlera")] + [InlineData(typeof(TestApplicationA), typeof(TestEventB), "webexpress.webcore.test.testeventhandlerb")] + [InlineData(typeof(TestApplicationB), typeof(TestEventA), "webexpress.webcore.test.testeventhandlera")] + [InlineData(typeof(TestApplicationB), typeof(TestEventB), "webexpress.webcore.test.testeventhandlerb")] + public void Id(Type applicationType, Type eventType, string id) + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + var application = componentHub.ApplicationManager.GetApplications(applicationType).FirstOrDefault(); + + // test execution + var eventHandlers = componentHub.EventManager.GetEventHandlers(application, eventType); + + if (id == null) + { + Assert.Empty(eventHandlers); + return; + } + + Assert.Contains(id, eventHandlers.Select(x => x.EventHandlerId)); + } + + /// + /// Test the id property of the event handler. + /// + [Theory] + [InlineData(typeof(TestApplicationA), typeof(TestEventA), "webexpress.webcore.test.testeventa")] + [InlineData(typeof(TestApplicationA), typeof(TestEventB), "webexpress.webcore.test.testeventb")] + [InlineData(typeof(TestApplicationB), typeof(TestEventA), "webexpress.webcore.test.testeventa")] + [InlineData(typeof(TestApplicationB), typeof(TestEventB), "webexpress.webcore.test.testeventb")] + public void EventId(Type applicationType, Type eventType, string id) + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + var application = componentHub.ApplicationManager.GetApplications(applicationType).FirstOrDefault(); + + // test execution + var eventHandlers = componentHub.EventManager.GetEventHandlers(application, eventType); + + if (id == null) + { + Assert.Empty(eventHandlers); + return; + } + + Assert.Contains(id, eventHandlers?.Select(x => x.EventId.ToString())); + } + + /// + /// Test raise the event handler. + /// + [Fact] + public void RaiseEventA1() + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + var application = componentHub.ApplicationManager.GetApplication(); + + // test execution + var eventArgument = new TestEventArgument() { TestProperty = false }; + + componentHub.EventManager.RaiseEvent(application, this, eventArgument); + + Assert.True(eventArgument.TestProperty); + } + + /// + /// Test raise the event handler. + /// + [Fact] + public void RaiseEventB1() + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + var application = componentHub.ApplicationManager.GetApplication(); + + // test execution + var eventArgument = new TestEventArgument() { TestProperty = false }; + + componentHub.EventManager.RaiseEvent(application, this, eventArgument); + + Assert.True(eventArgument.TestProperty); + } + } +} diff --git a/src/WebExpress.WebCore.Test/Manager/UnitTestFragmentManager.cs b/src/WebExpress.WebCore.Test/Manager/UnitTestFragmentManager.cs new file mode 100644 index 0000000..35df62c --- /dev/null +++ b/src/WebExpress.WebCore.Test/Manager/UnitTestFragmentManager.cs @@ -0,0 +1,145 @@ +using WebExpress.WebCore.Test.Fixture; +using WebExpress.WebCore.Test.WWW; +using WebExpress.WebCore.WebComponent; +using WebExpress.WebCore.WebFragment; +using WebExpress.WebCore.WebPage; +using WebExpress.WebCore.WebScope; + +namespace WebExpress.WebCore.Test.Manager +{ + /// + /// Test the fragment manager. + /// + [Collection("NonParallelTests")] + public class UnitTestFragmentManager + { + /// + /// Test the register function of the fragment manager. + /// + [Fact] + public void Register() + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + + // test execution + Assert.Equal(15, componentHub.FragmentManager.Fragments.Count()); + } + + /// + /// Test the remove function of the fragment manager. + /// + [Fact] + public void Remove() + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + var fragmentManager = componentHub.FragmentManager as FragmentManager; + var plugin = componentHub.PluginManager.GetPlugin(typeof(TestPlugin)); + + // test execution + fragmentManager.Remove(plugin); + + Assert.Empty(componentHub.FragmentManager.Fragments); + } + + /// + /// Tests whether the fragment manager implements interface IComponentManager. + /// + [Fact] + public void IsIComponentManager() + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + + // test execution + Assert.True(typeof(IComponentManager).IsAssignableFrom(componentHub.EventManager.GetType())); + } + + /// + /// Test the id property of the fragment. + /// + [Theory] + [InlineData(typeof(TestApplicationA), typeof(TestFragmentA), "webexpress.webcore.test.testfragmenta")] + [InlineData(typeof(TestApplicationB), typeof(TestFragmentA), "webexpress.webcore.test.testfragmenta")] + [InlineData(typeof(TestApplicationC), typeof(TestFragmentA), "webexpress.webcore.test.testfragmenta")] + [InlineData(typeof(TestApplicationA), typeof(TestFragmentB), "webexpress.webcore.test.testfragmentb")] + [InlineData(typeof(TestApplicationB), typeof(TestFragmentB), "webexpress.webcore.test.testfragmentb")] + [InlineData(typeof(TestApplicationC), typeof(TestFragmentB), "webexpress.webcore.test.testfragmentb")] + [InlineData(typeof(TestApplicationA), typeof(TestFragmentC), "webexpress.webcore.test.testfragmentc")] + [InlineData(typeof(TestApplicationB), typeof(TestFragmentC), "webexpress.webcore.test.testfragmentc")] + [InlineData(typeof(TestApplicationC), typeof(TestFragmentC), "webexpress.webcore.test.testfragmentc")] + public void Id(Type applicationType, Type fragmentType, string id) + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + var application = componentHub.ApplicationManager.GetApplications(applicationType).FirstOrDefault(); + + // test execution + var fragment = componentHub.FragmentManager.GetFragments(application, fragmentType); + + if (id == null) + { + Assert.Empty(fragment); + return; + } + + Assert.Contains(id, fragment.Select(x => x.FragmentId?.ToString())); + } + + /// + /// Test the get fragment function of the fragment. + /// + [Theory] + [InlineData(typeof(TestApplicationA), typeof(IScope), 0)] + [InlineData(typeof(TestApplicationA), typeof(TestScopeA), 1)] + [InlineData(typeof(TestApplicationA), typeof(About), 1)] + [InlineData(typeof(TestApplicationB), typeof(IScope), 0)] + [InlineData(typeof(TestApplicationB), typeof(About), 1)] + public void GetFragments(Type applicationType, Type scopeType, int count) + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + var application = componentHub.ApplicationManager.GetApplications(applicationType).FirstOrDefault(); + var renderContext = UnitTestFixture.CrerateRenderContextMock(application, [scopeType]); + + // test execution + var fragments = componentHub.FragmentManager.GetFragments(application, renderContext?.PageContext?.Scopes).ToList(); + + Assert.NotNull(fragments); + Assert.Equal(count, fragments.Count); + } + + /// + /// Test the Render function of the fragment. + /// + [Theory] + [InlineData(typeof(TestApplicationA), typeof(TestSectionA), typeof(TestScopeA), false)] + [InlineData(typeof(TestApplicationA), typeof(TestSectionA), typeof(TestScopeB), false)] + [InlineData(typeof(TestApplicationA), typeof(TestSectionA), typeof(About), false)] + [InlineData(typeof(TestApplicationA), typeof(TestSectionA), typeof(IScope), true)] + [InlineData(typeof(TestApplicationA), typeof(TestSectionA), typeof(TestScopeD), true)] + public void Render(Type applicationType, Type sectionType, Type scopeType, bool empty) + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + var application = componentHub.ApplicationManager.GetApplications(applicationType).FirstOrDefault(); + var renderContext = UnitTestFixture.CrerateRenderContextMock(application, [scopeType]); + var visualTree = new VisualTree(); + + // test execution + var html = componentHub.FragmentManager.Render(renderContext, visualTree, sectionType); + + Assert.NotNull(html); + + if (!empty) + { + Assert.NotEmpty(html.FirstOrDefault()?.ToString()); + } + else + { + Assert.Null(html.FirstOrDefault()); + } + } + } +} diff --git a/src/WebExpress.WebCore.Test/Manager/UnitTestIdentityManager.cs b/src/WebExpress.WebCore.Test/Manager/UnitTestIdentityManager.cs new file mode 100644 index 0000000..70664ca --- /dev/null +++ b/src/WebExpress.WebCore.Test/Manager/UnitTestIdentityManager.cs @@ -0,0 +1,211 @@ +using System.Security; +using WebExpress.WebCore.Test.Data; +using WebExpress.WebCore.Test.Fixture; +using WebExpress.WebCore.WebComponent; +using WebExpress.WebCore.WebIdentity; + +namespace WebExpress.WebCore.Test.Manager +{ + /// + /// Test the identity manager. + /// + [Collection("NonParallelTests")] + public class UnitTestIdentityManager + { + /// + /// Test the register function of the identity manager. + /// + [Fact] + public void Register() + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + + // test execution + Assert.Equal(9, componentHub.IdentityManager.Permissions.Count()); + Assert.Equal(6, componentHub.IdentityManager.Roles.Count()); + } + + /// + /// Test the remove function of the identity manager. + /// + [Fact] + public void Remove() + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + var plugin = componentHub.PluginManager.GetPlugin(typeof(TestPlugin)); + var identityManager = componentHub.IdentityManager as IdentityManager; + + // test execution + identityManager.Remove(plugin); + + Assert.Empty(componentHub.IdentityManager.Permissions); + Assert.Empty(componentHub.IdentityManager.Roles); + } + + /// + /// Tests whether the identity manager implements interface IComponentManager. + /// + [Fact] + public void IsIComponentManager() + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + + // test execution + Assert.True(typeof(IComponentManager).IsAssignableFrom(componentHub.IdentityManager.GetType())); + } + + /// + /// Test the CheckAccess function of the identity manager. + /// + [Theory] + [InlineData(typeof(TestApplicationA), "Alice", typeof(TestIdentityPermissionA), true)] + [InlineData(typeof(TestApplicationA), "Alice", typeof(TestIdentityPermissionB), true)] + [InlineData(typeof(TestApplicationA), "Alice", typeof(TestIdentityPermissionC), true)] + [InlineData(typeof(TestApplicationA), "Bob", typeof(TestIdentityPermissionA), true)] + [InlineData(typeof(TestApplicationA), "Bob", typeof(TestIdentityPermissionB), true)] + [InlineData(typeof(TestApplicationA), "Bob", typeof(TestIdentityPermissionC), false)] + [InlineData(typeof(TestApplicationA), "Charlie", typeof(TestIdentityPermissionA), false)] + [InlineData(typeof(TestApplicationA), "Charlie", typeof(TestIdentityPermissionB), false)] + [InlineData(typeof(TestApplicationA), "Charlie", typeof(TestIdentityPermissionC), false)] + public void CheckAccessIdentity(Type application, string identityName, Type permission, bool expected) + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + var identityManager = componentHub.IdentityManager as IdentityManager; + var applicationContext = componentHub.ApplicationManager.GetApplications(application).FirstOrDefault(); + var identity = MockIdentityFactory.GetIdentity(identityName); + + // test execution + var access = identityManager.CheckAccess(applicationContext, identity, permission); + + Assert.Equal(expected, access); + } + + /// + /// Test the CheckAccess function of the identity manager. + /// + [Theory] + [InlineData(typeof(TestApplicationA), "Admins", typeof(TestIdentityPermissionA), true)] + [InlineData(typeof(TestApplicationA), "Admins", typeof(TestIdentityPermissionB), true)] + [InlineData(typeof(TestApplicationA), "Admins", typeof(TestIdentityPermissionC), true)] + [InlineData(typeof(TestApplicationA), "Users", typeof(TestIdentityPermissionA), true)] + [InlineData(typeof(TestApplicationA), "Users", typeof(TestIdentityPermissionB), true)] + [InlineData(typeof(TestApplicationA), "Users", typeof(TestIdentityPermissionC), false)] + [InlineData(typeof(TestApplicationA), "Guests", typeof(TestIdentityPermissionA), false)] + [InlineData(typeof(TestApplicationA), "Guests", typeof(TestIdentityPermissionB), false)] + [InlineData(typeof(TestApplicationA), "Guests", typeof(TestIdentityPermissionC), false)] + public void CheckAccessGroup(Type application, string groupName, Type permission, bool expected) + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + var identityManager = componentHub.IdentityManager as IdentityManager; + var applicationContext = componentHub.ApplicationManager.GetApplications(application).FirstOrDefault(); + var group = MockIdentityFactory.GetIdentityGroup(groupName); + + // test execution + var access = identityManager.CheckAccess(applicationContext, group, permission); + + Assert.Equal(expected, access); + } + + /// + /// Test the CheckAccess function of the identity manager. + /// + [Theory] + [InlineData(typeof(TestApplicationA), typeof(TestIdentityRoleA), typeof(TestIdentityPermissionA), true)] + [InlineData(typeof(TestApplicationA), typeof(TestIdentityRoleA), typeof(TestIdentityPermissionB), true)] + [InlineData(typeof(TestApplicationA), typeof(TestIdentityRoleA), typeof(TestIdentityPermissionC), true)] + [InlineData(typeof(TestApplicationA), typeof(TestIdentityRoleB), typeof(TestIdentityPermissionA), true)] + [InlineData(typeof(TestApplicationA), typeof(TestIdentityRoleB), typeof(TestIdentityPermissionB), true)] + [InlineData(typeof(TestApplicationA), typeof(TestIdentityRoleB), typeof(TestIdentityPermissionC), false)] + public void CheckAccessRole(Type application, Type role, Type permission, bool expected) + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + var identityManager = componentHub.IdentityManager as IdentityManager; + var applicationContext = componentHub.ApplicationManager.GetApplications(application).FirstOrDefault(); + + // test execution + var access = identityManager.CheckAccess(applicationContext, role, permission); + + Assert.Equal(expected, access); + } + + /// + /// Test the Login function of the identity manager. + /// + [Theory] + [InlineData("Alice", "abc", true)] + [InlineData("Alice", "123", false)] + public void Login(string identityName, string password, bool expected) + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + var identityManager = componentHub.IdentityManager as IdentityManager; + var request = UnitTestFixture.CrerateRequestMock(); + var identity = MockIdentityFactory.GetIdentity(identityName); + var securePassword = new SecureString(); + password.ToList().ForEach(x => securePassword.AppendChar(x)); + securePassword.MakeReadOnly(); + + // test execution + var res = identityManager.Login(request, identity, securePassword); + + Assert.Equal(expected, res); + } + + /// + /// Test the Login function of the identity manager. + /// + [Theory] + [InlineData("Alice", "abc")] + [InlineData("Bob", "abc")] + [InlineData("Charlie", "abc")] + public void Logout(string identityName, string password) + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + var identityManager = componentHub.IdentityManager as IdentityManager; + var request = UnitTestFixture.CrerateRequestMock(); + var identity = MockIdentityFactory.GetIdentity(identityName); + var securePassword = new SecureString(); + password.ToList().ForEach(x => securePassword.AppendChar(x)); + securePassword.MakeReadOnly(); + identityManager.Login(request, identity, securePassword); + + // test execution + identityManager.Logout(request); + + var res = identityManager.GetCurrentIdentity(request); + Assert.Null(res); + } + + /// + /// Test the GetCurrentIdentity function of the identity manager. + /// + [Theory] + [InlineData("Alice", "abc")] + [InlineData("Bob", "abc")] + [InlineData("Charlie", "abc")] + public void GetCurrentIdentity(string identityName, string password) + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + var identityManager = componentHub.IdentityManager as IdentityManager; + var request = UnitTestFixture.CrerateRequestMock(); + var identity = MockIdentityFactory.GetIdentity(identityName); + var securePassword = new SecureString(); + password.ToList().ForEach(x => securePassword.AppendChar(x)); + securePassword.MakeReadOnly(); + identityManager.Login(request, identity, securePassword); + + // test execution + var res = identityManager.GetCurrentIdentity(request); + + Assert.Equal(identity, res); + } + } +} diff --git a/src/WebExpress.WebCore.Test/Manager/UnitTestInternationalization.cs b/src/WebExpress.WebCore.Test/Manager/UnitTestInternationalization.cs new file mode 100644 index 0000000..9b3247c --- /dev/null +++ b/src/WebExpress.WebCore.Test/Manager/UnitTestInternationalization.cs @@ -0,0 +1,136 @@ +using System.Globalization; +using WebExpress.WebCore.Internationalization; +using WebExpress.WebCore.Test.Fixture; +using WebExpress.WebCore.WebComponent; +using WebExpress.WebCore.WebPlugin; + +namespace WebExpress.WebCore.Test.Manager +{ + /// + /// Test the internationalization manager. + /// + [Collection("NonParallelTests")] + public class UnitTestInternationalization + { + /// + /// Test the register function of the internationalization manager. + /// + [Fact] + public void Register() + { + // preconditions + var componentHub = UnitTestFixture.CreateComponentHubMock(); + var pluginManager = componentHub.PluginManager as PluginManager; + + // test execution + pluginManager.Register(); + + Assert.Equal("This is a test", I18N.Translate("webexpress.webcore.test:unit.test.message")); + } + + /// + /// Test the remove function of the internationalization manager. + /// + [Fact] + public void Remove() + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + var internationalizationManager = componentHub.InternationalizationManager as InternationalizationManager; + var plugin = componentHub.PluginManager.GetPlugin(typeof(TestPlugin)); + + // test execution + internationalizationManager.Remove(plugin); + + Assert.Equal("webexpress.webcore.test:unit.test.message", I18N.Translate("webexpress.webcore.test:unit.test.message")); + } + + /// + /// Test the default culture property of the internationalization manager. + /// + [Fact] + public void GetDefaultCulture() + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + + // test execution + Assert.Equal(CultureInfo.GetCultureInfo("en"), InternationalizationManager.DefaultCulture); + } + + /// + /// Test the translate function of the internationalization manager. + /// + [Theory] + [InlineData("webexpress.webcore.test:unit.test.message", "This is a test")] + [InlineData("webexpress.webcore.test:unit.test.message", "This is a test", "en")] + [InlineData("webexpress.webcore.test:unit.test.message", "Dies ist ein Test", "de")] + [InlineData("webexpress.webcore.test:unit.test.message", "Dies ist ein Test", "de", "webexpress.webcore.test")] + [InlineData("webexpress.webcore.test:welcome.message", "Welcome 'Max' to our application!", "en", null, "Max")] + [InlineData("welcome.message", "Welcome 'Max' to our application!", "en", "webexpress.webcore.test", "Max")] + [InlineData("webexpress.webcore:app.startup", "Startup", "en", null)] + [InlineData("non.existent.key", "non.existent.key", "de")] + public void Translate(string key, string excepted, string cultureName = null, string pluginID = null, params object[] param) + { + // preconditions + UnitTestFixture.CreateAndRegisterComponentHubMock(); + + if (cultureName == null && !param.Any()) + { + // test execution + var result = I18N.Translate(key); + + Assert.Equal(excepted, result); + } + if (cultureName == null && param.Any()) + { + // test execution + var result = I18N.Translate(key, param); + + Assert.Equal(excepted, result); + } + if (cultureName != null && pluginID == null && !param.Any()) + { + // test execution + var result = I18N.Translate(CultureInfo.GetCultureInfo(cultureName), key); + + Assert.Equal(excepted, result); + } + if (cultureName != null && pluginID == null && param.Any()) + { + // test execution + var result = I18N.Translate(CultureInfo.GetCultureInfo(cultureName), key, param); + + Assert.Equal(excepted, result); + } + if (cultureName != null && pluginID != null && !param.Any()) + { + // test execution + var result = I18N.Translate(CultureInfo.GetCultureInfo(cultureName), pluginID, key); + + Assert.Equal(excepted, result); + } + if (cultureName != null && pluginID != null && param.Any()) + { + // test execution + var result = I18N.Translate(CultureInfo.GetCultureInfo(cultureName), pluginID, key, param); + + Assert.Equal(excepted, result); + } + + } + + /// + /// Tests whether the internationalization manager implements interface IComponentManager. + /// + [Fact] + public void IsIComponentManager() + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + + // test execution + Assert.True(typeof(IComponentManager).IsAssignableFrom(componentHub.InternationalizationManager.GetType())); + } + } +} diff --git a/src/WebExpress.WebCore.Test/Manager/UnitTestJobManager.cs b/src/WebExpress.WebCore.Test/Manager/UnitTestJobManager.cs new file mode 100644 index 0000000..3f4eb85 --- /dev/null +++ b/src/WebExpress.WebCore.Test/Manager/UnitTestJobManager.cs @@ -0,0 +1,113 @@ +using WebExpress.WebCore.Test.Fixture; +using WebExpress.WebCore.WebComponent; +using WebExpress.WebCore.WebJob; + +namespace WebExpress.WebCore.Test.Manager +{ + /// + /// Test the job manager. + /// + [Collection("NonParallelTests")] + public class UnitTestJobManager + { + /// + /// Test the register function of the job manager. + /// + [Fact] + public void Register() + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + + // test execution + Assert.Equal(3, componentHub.JobManager.Jobs.Count()); + } + + /// + /// Test the remove function of the job manager. + /// + [Fact] + public void Remove() + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + var plugin = componentHub.PluginManager.GetPlugin(typeof(TestPlugin)); + var jobManager = componentHub.JobManager as JobManager; + + // test execution + jobManager.Remove(plugin); + + Assert.Empty(componentHub.JobManager.Jobs); + } + + /// + /// Tests whether the job manager implements interface IComponentManager. + /// + [Fact] + public void IsIComponentManager() + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + + // test execution + Assert.True(typeof(IComponentManager).IsAssignableFrom(componentHub.ResourceManager.GetType())); + } + + /// + /// Tests whether the job context implements interface IContext. + /// + [Fact] + public void IsIContext() + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + + // test execution + foreach (var job in componentHub.JobManager.Jobs) + { + Assert.True(typeof(IContext).IsAssignableFrom(job.GetType()), $"Job context {job.GetType().Name} does not implement IContext."); + } + } + + /// + /// Test the id property of the job. + /// + [Theory] + [InlineData(typeof(TestApplicationA), typeof(TestJobA), "webexpress.webcore.test.testjoba")] + [InlineData(typeof(TestApplicationB), typeof(TestJobA), "webexpress.webcore.test.testjoba")] + [InlineData(typeof(TestApplicationC), typeof(TestJobA), "webexpress.webcore.test.testjoba")] + + public void Id(Type applicationType, Type jobType, string id) + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + var application = componentHub.ApplicationManager.GetApplications(applicationType).FirstOrDefault(); + var job = componentHub.JobManager.GetJob(application, jobType); + + // test execution + Assert.Equal(id, job?.JobId.ToString()); + } + + /// + /// Test the cron property of the job. + /// + [Theory] + [InlineData(typeof(TestApplicationA), typeof(TestJobA), 50, 8, 31, new[] { 1, 2 }, 0)] + [InlineData(typeof(TestApplicationB), typeof(TestJobA), 50, 8, 31, new[] { 1, 2 }, 0)] + [InlineData(typeof(TestApplicationC), typeof(TestJobA), 50, 8, 31, new[] { 1, 2 }, 0)] + public void Cron(Type applicationType, Type jobType, int minute, int hour, int day, int[] month, int weekday) + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + var application = componentHub.ApplicationManager.GetApplications(applicationType).FirstOrDefault(); + var job = componentHub.JobManager.GetJob(application, jobType); + + // test execution + Assert.Equal(minute, job?.Cron.Minute.FirstOrDefault() ?? -1); + Assert.Equal(hour, job?.Cron.Hour.FirstOrDefault() ?? -1); + Assert.Equal(day, job?.Cron.Day.FirstOrDefault() ?? -1); + Assert.True(month.SequenceEqual(job?.Cron.Month ?? [])); + Assert.Equal(weekday, job?.Cron.Weekday.FirstOrDefault() ?? -1); + } + } +} diff --git a/src/WebExpress.WebCore.Test/Manager/UnitTestLogManager.cs b/src/WebExpress.WebCore.Test/Manager/UnitTestLogManager.cs new file mode 100644 index 0000000..96f8c3d --- /dev/null +++ b/src/WebExpress.WebCore.Test/Manager/UnitTestLogManager.cs @@ -0,0 +1,55 @@ +using WebExpress.WebCore.Test.Fixture; +using WebExpress.WebCore.WebComponent; +using WebExpress.WebCore.WebLog; + +namespace WebExpress.WebCore.Test.Manager +{ + /// + /// Test the log manager. + /// + [Collection("NonParallelTests")] + public class UnitTestLogManager + { + /// + /// Test the register function of the log manager. + /// + [Fact] + public void Register() + { + // preconditions + var componentHub = UnitTestFixture.CreateComponentHubMock(); + var logManager = componentHub.LogManager as LogManager; + + // test execution + Assert.NotNull(logManager); + } + + /// + /// Test the remove function of the log manager. + /// + [Fact] + public void Remove() + { + // preconditions + var componentHub = UnitTestFixture.CreateComponentHubMock(); + var logManager = componentHub.LogManager as LogManager; + + // test execution + Assert.NotNull(logManager); + } + + /// + /// Tests whether the log manager implements interface IComponentManager. + /// + [Fact] + public void IsIComponentManager() + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + var logManager = componentHub.LogManager as LogManager; + + // test execution + Assert.True(typeof(IComponentManager).IsAssignableFrom(logManager.GetType())); + } + } +} diff --git a/src/WebExpress.WebCore.Test/Manager/UnitTestPackageManager.cs b/src/WebExpress.WebCore.Test/Manager/UnitTestPackageManager.cs new file mode 100644 index 0000000..dd8c04c --- /dev/null +++ b/src/WebExpress.WebCore.Test/Manager/UnitTestPackageManager.cs @@ -0,0 +1,55 @@ +using WebExpress.WebCore.Test.Fixture; +using WebExpress.WebCore.WebComponent; +using WebExpress.WebCore.WebPackage; + +namespace WebExpress.WebCore.Test.Manager +{ + /// + /// Test the package manager. + /// + [Collection("NonParallelTests")] + public class UnitTestPackageManager + { + /// + /// Test the register function of the package manager. + /// + [Fact] + public void Register() + { + // preconditions + var componentHub = UnitTestFixture.CreateComponentHubMock(); + var packageManager = componentHub.PackageManager as PackageManager; + + // test execution + Assert.NotNull(packageManager); + } + + /// + /// Test the remove function of the package manager. + /// + [Fact] + public void Remove() + { + // preconditions + var componentHub = UnitTestFixture.CreateComponentHubMock(); + var packageManager = componentHub.PackageManager as PackageManager; + + // test execution + Assert.NotNull(packageManager); + } + + /// + /// Tests whether the package manager implements interface IComponentManager. + /// + [Fact] + public void IsIComponentManager() + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + var packageManager = componentHub.PackageManager as PackageManager; + + // test execution + Assert.True(typeof(IComponentManager).IsAssignableFrom(packageManager.GetType())); + } + } +} diff --git a/src/WebExpress.WebCore.Test/Manager/UnitTestPageManager.cs b/src/WebExpress.WebCore.Test/Manager/UnitTestPageManager.cs new file mode 100644 index 0000000..fc39f30 --- /dev/null +++ b/src/WebExpress.WebCore.Test/Manager/UnitTestPageManager.cs @@ -0,0 +1,148 @@ +using WebExpress.WebCore.Test.Fixture; +using WebExpress.WebCore.Test.WWW; +using WebExpress.WebCore.WebComponent; +using WebExpress.WebCore.WebPage; + +namespace WebExpress.WebCore.Test.Manager +{ + /// + /// Test the page manager. + /// + [Collection("NonParallelTests")] + public class UnitTestPageManager + { + /// + /// Test the register function of the page manager. + /// + [Fact] + public void Register() + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + + // test execution + Assert.Equal(33, componentHub.PageManager.Pages.Count()); + } + + /// + /// Test the remove function of the page manager. + /// + [Fact] + public void Remove() + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + var plugin = componentHub.PluginManager.GetPlugin(typeof(TestPlugin)); + var pageManager = componentHub.PageManager as PageManager; + + // test execution + pageManager.Remove(plugin); + + Assert.Empty(componentHub.PageManager.Pages); + } + + /// + /// Test the id property of the page. + /// + [Theory] + [InlineData(typeof(TestApplicationA), typeof(WWW.Index), "webexpress.webcore.test.www.index")] + [InlineData(typeof(TestApplicationA), typeof(About), "webexpress.webcore.test.www.about")] + [InlineData(typeof(TestApplicationA), typeof(Contact), "webexpress.webcore.test.www.contact")] + [InlineData(typeof(TestApplicationA), typeof(WWW.Blog.Index), "webexpress.webcore.test.www.blog.index")] + [InlineData(typeof(TestApplicationA), typeof(WWW.Blog.Post.Index), "webexpress.webcore.test.www.blog.post.index")] + [InlineData(typeof(TestApplicationA), typeof(WWW.Blog.Post.Add), "webexpress.webcore.test.www.blog.post.add")] + [InlineData(typeof(TestApplicationA), typeof(WWW.Blog.Post.PostId.Edit), "webexpress.webcore.test.www.blog.post.postid.edit")] + [InlineData(typeof(TestApplicationA), typeof(WWW.Blog.Post.PostId.Index), "webexpress.webcore.test.www.blog.post.postid.index")] + [InlineData(typeof(TestApplicationA), typeof(WWW.Products.Index), "webexpress.webcore.test.www.products.index")] + [InlineData(typeof(TestApplicationA), typeof(WWW.Products.List), "webexpress.webcore.test.www.products.list")] + [InlineData(typeof(TestApplicationA), typeof(WWW.Products.Details.Index), "webexpress.webcore.test.www.products.details.index")] + public void Id(Type applicationType, Type pageType, string id) + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + var application = componentHub.ApplicationManager.GetApplications(applicationType)?.FirstOrDefault(); + var page = componentHub.PageManager.GetPages(pageType, application)?.FirstOrDefault(); + + // test execution + Assert.Equal(id, page.EndpointId.ToString()); + } + + /// + /// Test the title property of the page. + /// + [Theory] + [InlineData(typeof(TestApplicationA), typeof(WWW.Index), "webindex:home.label")] + [InlineData(typeof(TestApplicationA), typeof(About), "webindex:about.label")] + [InlineData(typeof(TestApplicationA), typeof(Contact), "webindex:contact.label")] + [InlineData(typeof(TestApplicationB), typeof(WWW.Index), "webindex:home.label")] + [InlineData(typeof(TestApplicationB), typeof(About), "webindex:about.label")] + [InlineData(typeof(TestApplicationB), typeof(Contact), "webindex:contact.label")] + [InlineData(typeof(TestApplicationC), typeof(WWW.Index), "webindex:home.label")] + [InlineData(typeof(TestApplicationC), typeof(About), "webindex:about.label")] + [InlineData(typeof(TestApplicationC), typeof(Contact), "webindex:contact.label")] + + public void Title(Type applicationType, Type resourceType, string title) + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + var application = componentHub.ApplicationManager.GetApplications(applicationType)?.FirstOrDefault(); + var page = componentHub.PageManager.GetPages(resourceType, application)?.FirstOrDefault(); + + // test execution + Assert.Equal(title, page.PageTitle); + } + + /// + /// Test the context path property of the page. + /// + [Theory] + [InlineData(typeof(TestApplicationA), typeof(WWW.Index), "/server/appa")] + [InlineData(typeof(TestApplicationA), typeof(About), "/server/appa/about")] + [InlineData(typeof(TestApplicationA), typeof(Contact), "/server/appa/contact")] + [InlineData(typeof(TestApplicationB), typeof(WWW.Index), "/server/appb")] + [InlineData(typeof(TestApplicationB), typeof(About), "/server/appb/about")] + [InlineData(typeof(TestApplicationB), typeof(Contact), "/server/appb/contact")] + [InlineData(typeof(TestApplicationC), typeof(WWW.Index), "/server")] + [InlineData(typeof(TestApplicationC), typeof(About), "/server/about")] + [InlineData(typeof(TestApplicationC), typeof(Contact), "/server/contact")] + public void RoutePath(Type applicationType, Type resourceType, string path) + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + var application = componentHub.ApplicationManager.GetApplications(applicationType)?.FirstOrDefault(); + var page = componentHub.PageManager.GetPages(resourceType, application)?.FirstOrDefault(); + + // test execution + Assert.Equal(path, page.Route.ToString()); + } + + /// + /// Tests whether the page manager implements interface IComponentManager. + /// + [Fact] + public void IsIComponentManager() + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + + // test execution + Assert.True(typeof(IComponentManager).IsAssignableFrom(componentHub.PageManager.GetType())); + } + + /// + /// Tests whether the page context implements interface IContext. + /// + [Fact] + public void IsIContext() + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + + // test execution + foreach (var pages in componentHub.PageManager.Pages) + { + Assert.True(typeof(IContext).IsAssignableFrom(pages.GetType()), $"Page context {pages.GetType().Name} does not implement IContext."); + } + } + } +} diff --git a/src/WebExpress.WebCore.Test/Manager/UnitTestPluginManager.cs b/src/WebExpress.WebCore.Test/Manager/UnitTestPluginManager.cs new file mode 100644 index 0000000..2b1d5d7 --- /dev/null +++ b/src/WebExpress.WebCore.Test/Manager/UnitTestPluginManager.cs @@ -0,0 +1,257 @@ +using WebExpress.WebCore.Test.Fixture; +using WebExpress.WebCore.WebComponent; +using WebExpress.WebCore.WebPlugin; + +namespace WebExpress.WebCore.Test.Manager +{ + /// + /// Test the plugin manager. + /// + [Collection("NonParallelTests")] + public class UnitTestPluginManager + { + /// + /// Test the register function of the plugin manager. + /// + [Fact] + public void Register() + { + // preconditions + var componentHub = UnitTestFixture.CreateComponentHubMock(); + var pluginManager = componentHub.PluginManager as PluginManager; + + // test execution + pluginManager.Register(); + + Assert.Single(componentHub.PluginManager.Plugins); + Assert.Contains("webexpress.webcore.test", componentHub.PluginManager.GetPlugin(typeof(TestPlugin))?.PluginId.ToString()); + } + + /// + /// Test the event of the plugin manager. + /// + [Fact] + public void RegisterEvent() + { + // preconditions + var componentHub = UnitTestFixture.CreateComponentHubMock(); + var pluginManager = componentHub.PluginManager as PluginManager; + var i = 0; + var triggered = false; + + componentHub.PluginManager.AddPlugin += (s, e) => { i++; triggered = true; }; + + // test execution + pluginManager.Register(); + + Assert.Single(componentHub.PluginManager.Plugins); + Assert.Contains("webexpress.webcore.test", componentHub.PluginManager.GetPlugin(typeof(TestPlugin))?.PluginId.ToString()); + Assert.Equal(1, i); + Assert.True(triggered); + } + + /// + /// Test the remove function of the plugin manager. + /// + [Fact] + public void Remove() + { + // preconditions + var componentHub = UnitTestFixture.CreateComponentHubMock(); + var pluginManager = componentHub.PluginManager as PluginManager; + pluginManager.Register(); + var plugin = componentHub.PluginManager.GetPlugin(typeof(TestPlugin)); + + // test execution + pluginManager.Remove(plugin); + + Assert.Empty(componentHub.PluginManager.Plugins); + } + + /// + /// Test the event of the plugin manager. + /// + [Fact] + public void RemoveEvent() + { + // preconditions + var componentHub = UnitTestFixture.CreateComponentHubMock(); + var pluginManager = componentHub.PluginManager as PluginManager; + var i = 1; + var triggered = false; + + componentHub.PluginManager.RemovePlugin += (s, e) => { i--; triggered = true; }; + pluginManager.Register(); + var plugin = componentHub.PluginManager.GetPlugin(typeof(TestPlugin)); + + // test execution + pluginManager.Remove(plugin); + + Assert.Empty(componentHub.PluginManager.Plugins); + Assert.Equal(0, i); + Assert.True(triggered); + } + + /// + /// Test the get plugin function of the plugin manager. + /// + [Fact] + public void GetPluginById() + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + + // test execution + var plugin = componentHub.PluginManager.GetPlugin("webexpress.webcore.test"); + + Assert.Equal("webexpress.webcore.test", plugin?.PluginId.ToString()); + } + + /// + /// Test the get plugin function of the plugin manager. + /// + [Fact] + public void GetPluginByType() + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + + // test execution + var plugin = componentHub.PluginManager.GetPlugin(typeof(TestPlugin)); + + Assert.Equal("webexpress.webcore.test", plugin?.PluginId.ToString()); + } + + /// + /// Test the name property of the plugin. + /// + [Fact] + public void Id() + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + var plugin = componentHub.PluginManager.GetPlugin(typeof(TestPlugin)); + + // test execution + Assert.Equal(typeof(TestPlugin).Namespace.ToLower(), plugin.PluginId.ToString()); + } + + /// + /// Test the name property of the plugin. + /// + [Fact] + public void GetName() + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + var plugin = componentHub.PluginManager.GetPlugin(typeof(TestPlugin)); + + // test execution + Assert.Equal("TestPlugin", plugin.PluginName); + } + + /// + /// Test the description property of the plugin. + /// + [Fact] + public void GetDescription() + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + var plugin = componentHub.PluginManager.GetPlugin(typeof(TestPlugin)); + + // test execution + Assert.Equal("plugin.description", plugin.Description); + } + + /// + /// Test the icon property of the plugin. + /// + [Fact] + public void GetIcon() + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + var plugin = componentHub.PluginManager.GetPlugin(typeof(TestPlugin)); + + // test execution + Assert.Equal("/server/assets/img/Logo.png", plugin.Icon.ToString()); + } + + /// + /// Test the boot function of the plugin manager. + /// + [Theory] + [InlineData("webexpress.webcore.test", "webexpress.webcore.test")] + [InlineData("non.existent.plugin", null)] + [InlineData("", null)] + [InlineData(null, null)] + public void Boot(string pluginId, string expected) + { + // preconditions + var componentHub = UnitTestFixture.CreateComponentHubMock(); + var pluginManager = componentHub.PluginManager as PluginManager; + pluginManager.Register(); + var plugin = componentHub.PluginManager.GetPlugin(pluginId); + + // test execution + pluginManager.Boot(plugin); + + Assert.Single(componentHub.PluginManager.Plugins); + Assert.Equal(expected, plugin?.PluginId.ToString()); + } + + /// + /// Test the shut down of the plugin manager. + /// + [Theory] + [InlineData("webexpress.webcore.test", "webexpress.webcore.test")] + [InlineData("non.existent.plugin", null)] + [InlineData("", null)] + [InlineData(null, null)] + public void ShutDown(string pluginId, string expected) + { + // preconditions + var componentHub = UnitTestFixture.CreateComponentHubMock(); + var pluginManager = componentHub.PluginManager as PluginManager; + pluginManager.Register(); + var plugin = componentHub.PluginManager.GetPlugin(pluginId); + + // test execution + pluginManager.ShutDown(plugin); + + Assert.Single(componentHub.PluginManager.Plugins); + Assert.Equal(expected, plugin?.PluginId.ToString()); + } + + /// + /// Tests whether the plugin manager implements interface IComponentManager. + /// + [Fact] + public void IsIComponentManager() + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + var pluginManager = componentHub.PluginManager as PluginManager; + + // test execution + Assert.True(typeof(IComponentManager).IsAssignableFrom(pluginManager.GetType())); + } + + /// + /// Tests whether the plugin context implements interface IContext. + /// + [Fact] + public void IsIContext() + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + + // test execution + foreach (var plugin in componentHub.PluginManager.Plugins) + { + Assert.True(typeof(IContext).IsAssignableFrom(plugin.GetType()), $"Plugin context {plugin.GetType().Name} does not implement IContext."); + } + } + } +} diff --git a/src/WebExpress.WebCore.Test/Manager/UnitTestResourceManager.cs b/src/WebExpress.WebCore.Test/Manager/UnitTestResourceManager.cs new file mode 100644 index 0000000..7a6728d --- /dev/null +++ b/src/WebExpress.WebCore.Test/Manager/UnitTestResourceManager.cs @@ -0,0 +1,128 @@ +using WebExpress.WebCore.Test.Fixture; +using WebExpress.WebCore.Test.WWW.Resources; +using WebExpress.WebCore.WebComponent; +using WebExpress.WebCore.WebResource; + +namespace WebExpress.WebCore.Test.Manager +{ + /// + /// Test the resource manager. + /// + [Collection("NonParallelTests")] + public class UnitTestResourceManager + { + /// + /// Test the register function of the resource manager. + /// + [Fact] + public void Register() + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + + // test execution + Assert.Equal(12, componentHub.ResourceManager.Resources.Count()); + } + + /// + /// Test the remove function of the resource manager. + /// + [Fact] + public void Remove() + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + var plugin = componentHub.PluginManager.GetPlugin(typeof(TestPlugin)); + var resourceManager = componentHub.ResourceManager as ResourceManager; + + // test execution + resourceManager.Remove(plugin); + + Assert.Empty(componentHub.ResourceManager.Resources); + } + + /// + /// Test the id property of the resource. + /// + [Theory] + [InlineData(typeof(TestApplicationA), typeof(TestResourceA), "webexpress.webcore.test.www.resources.testresourcea")] + [InlineData(typeof(TestApplicationA), typeof(TestResourceB), "webexpress.webcore.test.www.resources.testresourceb")] + [InlineData(typeof(TestApplicationA), typeof(TestResourceC), "webexpress.webcore.test.www.resources.testresourcec")] + [InlineData(typeof(TestApplicationA), typeof(TestResourceD), "webexpress.webcore.test.www.resources.testresourced")] + [InlineData(typeof(TestApplicationB), typeof(TestResourceA), "webexpress.webcore.test.www.resources.testresourcea")] + [InlineData(typeof(TestApplicationB), typeof(TestResourceB), "webexpress.webcore.test.www.resources.testresourceb")] + [InlineData(typeof(TestApplicationB), typeof(TestResourceC), "webexpress.webcore.test.www.resources.testresourcec")] + [InlineData(typeof(TestApplicationB), typeof(TestResourceD), "webexpress.webcore.test.www.resources.testresourced")] + [InlineData(typeof(TestApplicationC), typeof(TestResourceA), "webexpress.webcore.test.www.resources.testresourcea")] + [InlineData(typeof(TestApplicationC), typeof(TestResourceB), "webexpress.webcore.test.www.resources.testresourceb")] + [InlineData(typeof(TestApplicationC), typeof(TestResourceC), "webexpress.webcore.test.www.resources.testresourcec")] + [InlineData(typeof(TestApplicationC), typeof(TestResourceD), "webexpress.webcore.test.www.resources.testresourced")] + public void Id(Type applicationType, Type resourceType, string id) + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + var application = componentHub.ApplicationManager.GetApplications(applicationType)?.FirstOrDefault(); + var resource = componentHub.ResourceManager.GetResorces(resourceType, application)?.FirstOrDefault(); + + // test execution + Assert.Equal(id, resource?.EndpointId.ToString()); + } + + /// + /// Test the context path property of the resource. + /// + [Theory] + [InlineData(typeof(TestApplicationA), typeof(TestResourceA), "/server/appa/resources/testresourcea")] + [InlineData(typeof(TestApplicationA), typeof(TestResourceB), "/server/appa/resources/testresourceb")] + [InlineData(typeof(TestApplicationA), typeof(TestResourceC), "/server/appa/resources/testresourcec")] + [InlineData(typeof(TestApplicationA), typeof(TestResourceD), "/server/appa/resources/testresourced")] + [InlineData(typeof(TestApplicationB), typeof(TestResourceA), "/server/appb/resources/testresourcea")] + [InlineData(typeof(TestApplicationB), typeof(TestResourceB), "/server/appb/resources/testresourceb")] + [InlineData(typeof(TestApplicationB), typeof(TestResourceC), "/server/appb/resources/testresourcec")] + [InlineData(typeof(TestApplicationB), typeof(TestResourceD), "/server/appb/resources/testresourced")] + [InlineData(typeof(TestApplicationC), typeof(TestResourceA), "/server/resources/testresourcea")] + [InlineData(typeof(TestApplicationC), typeof(TestResourceB), "/server/resources/testresourceb")] + [InlineData(typeof(TestApplicationC), typeof(TestResourceC), "/server/resources/testresourcec")] + [InlineData(typeof(TestApplicationC), typeof(TestResourceD), "/server/resources/testresourced")] + + public void RoutePath(Type applicationType, Type resourceType, string path) + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + var application = componentHub.ApplicationManager.GetApplications(applicationType)?.FirstOrDefault(); + var resource = componentHub.ResourceManager.GetResorces(resourceType, application)?.FirstOrDefault(); + + // test execution + Assert.Equal(path, resource.Route.ToString()); + } + + /// + /// Tests whether the resource manager implements interface IComponentManager. + /// + [Fact] + public void IsIComponentManager() + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + + // test execution + Assert.True(typeof(IComponentManager).IsAssignableFrom(componentHub.ResourceManager.GetType())); + } + + /// + /// Tests whether the resource context implements interface IContext. + /// + [Fact] + public void IsIContext() + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + + // test execution + foreach (var resources in componentHub.ResourceManager.Resources) + { + Assert.True(typeof(IContext).IsAssignableFrom(resources.GetType()), $"Resource context {resources.GetType().Name} does not implement IContext."); + } + } + } +} diff --git a/src/WebExpress.WebCore.Test/Manager/UnitTestRestApiManager.cs b/src/WebExpress.WebCore.Test/Manager/UnitTestRestApiManager.cs new file mode 100644 index 0000000..c0d25cb --- /dev/null +++ b/src/WebExpress.WebCore.Test/Manager/UnitTestRestApiManager.cs @@ -0,0 +1,142 @@ +using WebExpress.WebCore.Test.Fixture; +using WebExpress.WebCore.Test.WWW.Api._1; +using WebExpress.WebCore.Test.WWW.Api._2; +using WebExpress.WebCore.Test.WWW.Api._3; +using WebExpress.WebCore.WebComponent; +using WebExpress.WebCore.WebRestApi; + +namespace WebExpress.WebCore.Test.Manager +{ + /// + /// Test the rest api manager. + /// + [Collection("NonParallelTests")] + public class UnitTestRestApiManager + { + /// + /// Test the register function of the rest api manager. + /// + [Fact] + public void Register() + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + + // test execution + Assert.Equal(9, componentHub.RestApiManager.RestApis.Count()); + } + + /// + /// Test the remove function of the rest api manager. + /// + [Fact] + public void Remove() + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + var plugin = componentHub.PluginManager.GetPlugin(typeof(TestPlugin)); + var apiManager = componentHub.RestApiManager as RestApiManager; + + // test execution + apiManager.Remove(plugin); + + Assert.Empty(componentHub.RestApiManager.RestApis); + } + + /// + /// Test the id property of the rest api. + /// + [Theory] + [InlineData(typeof(TestApplicationA), typeof(TestRestApiA), "webexpress.webcore.test.www.api._1.testrestapia")] + [InlineData(typeof(TestApplicationA), typeof(TestRestApiB), "webexpress.webcore.test.www.api._2.testrestapib")] + [InlineData(typeof(TestApplicationA), typeof(TestRestApiC), "webexpress.webcore.test.www.api._3.testrestapic")] + [InlineData(typeof(TestApplicationB), typeof(TestRestApiA), "webexpress.webcore.test.www.api._1.testrestapia")] + [InlineData(typeof(TestApplicationB), typeof(TestRestApiB), "webexpress.webcore.test.www.api._2.testrestapib")] + [InlineData(typeof(TestApplicationB), typeof(TestRestApiC), "webexpress.webcore.test.www.api._3.testrestapic")] + [InlineData(typeof(TestApplicationC), typeof(TestRestApiA), "webexpress.webcore.test.www.api._1.testrestapia")] + [InlineData(typeof(TestApplicationC), typeof(TestRestApiB), "webexpress.webcore.test.www.api._2.testrestapib")] + [InlineData(typeof(TestApplicationC), typeof(TestRestApiC), "webexpress.webcore.test.www.api._3.testrestapic")] + public void Id(Type applicationType, Type resourceType, string id) + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + var application = componentHub.ApplicationManager.GetApplications(applicationType)?.FirstOrDefault(); + var api = componentHub.RestApiManager.GetRestApi(resourceType, application)?.FirstOrDefault(); + + // test execution + Assert.Equal(id, api?.EndpointId.ToString()); + } + + /// + /// Test the context path property of the rest api. + /// + [Theory] + [InlineData(typeof(TestApplicationA), typeof(TestRestApiA), "/server/appa/api/1/testrestapia")] + [InlineData(typeof(TestApplicationA), typeof(TestRestApiB), "/server/appa/api/2/testrestapib")] + [InlineData(typeof(TestApplicationA), typeof(TestRestApiC), "/server/appa/api/3/testrestapic")] + [InlineData(typeof(TestApplicationB), typeof(TestRestApiA), "/server/appb/api/1/testrestapia")] + [InlineData(typeof(TestApplicationB), typeof(TestRestApiB), "/server/appb/api/2/testrestapib")] + [InlineData(typeof(TestApplicationB), typeof(TestRestApiC), "/server/appb/api/3/testrestapic")] + [InlineData(typeof(TestApplicationC), typeof(TestRestApiA), "/server/api/1/testrestapia")] + [InlineData(typeof(TestApplicationC), typeof(TestRestApiB), "/server/api/2/testrestapib")] + [InlineData(typeof(TestApplicationC), typeof(TestRestApiC), "/server/api/3/testrestapic")] + public void RoutePath(Type applicationType, Type resourceType, string path) + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + var application = componentHub.ApplicationManager.GetApplications(applicationType)?.FirstOrDefault(); + var api = componentHub.RestApiManager.GetRestApi(resourceType, application)?.FirstOrDefault(); + + // test execution + Assert.Equal(path, api?.Route.ToString()); + } + + /// + /// Test the context path property of the rest api. + /// + [Theory] + [InlineData(typeof(TestApplicationA), typeof(TestRestApiA), CrudMethod.POST)] + [InlineData(typeof(TestApplicationA), typeof(TestRestApiA), CrudMethod.GET)] + [InlineData(typeof(TestApplicationA), typeof(TestRestApiB), CrudMethod.GET)] + [InlineData(typeof(TestApplicationA), typeof(TestRestApiC), CrudMethod.GET)] + public void Method(Type applicationType, Type resourceType, CrudMethod method) + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + var application = componentHub.ApplicationManager.GetApplications(applicationType)?.FirstOrDefault(); + var api = componentHub.RestApiManager.GetRestApi(resourceType, application)?.FirstOrDefault(); + + // test execution + Assert.Contains(method, api?.Methods); + } + + /// + /// Tests whether the rest api manager implements interface IComponentManager. + /// + [Fact] + public void IsIComponentManager() + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + + // test execution + Assert.True(typeof(IComponentManager).IsAssignableFrom(componentHub.RestApiManager.GetType())); + } + + /// + /// Tests whether the rest api context implements interface IContext. + /// + [Fact] + public void IsIContext() + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + + // test execution + foreach (var api in componentHub.RestApiManager.RestApis) + { + Assert.True(typeof(IContext).IsAssignableFrom(api.GetType()), $"Api context {api.GetType().Name} does not implement IContext."); + } + } + } +} diff --git a/src/WebExpress.WebCore.Test/Manager/UnitTestSessionManager.cs b/src/WebExpress.WebCore.Test/Manager/UnitTestSessionManager.cs new file mode 100644 index 0000000..e7bf41e --- /dev/null +++ b/src/WebExpress.WebCore.Test/Manager/UnitTestSessionManager.cs @@ -0,0 +1,98 @@ +using WebExpress.WebCore.Test.Fixture; +using WebExpress.WebCore.WebComponent; +using WebExpress.WebCore.WebMessage; +using WebExpress.WebCore.WebSession.Model; + +namespace WebExpress.WebCore.Test.Manager +{ + /// + /// Test the session manager. + /// + [Collection("NonParallelTests")] + public class UnitTestSessionManager + { + /// + /// Test the register function of the session manager. + /// + [Fact] + public void Register() + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + + // test execution + Assert.NotNull(componentHub.SessionManager); + } + + /// + /// Tests whether the session manager implements interface IComponentManager. + /// + [Fact] + public void IsIComponentManager() + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + + // test execution + Assert.True(typeof(IComponentManager).IsAssignableFrom(componentHub.SessionManager.GetType())); + } + + /// + /// Test the GetSession function of the session manager. + /// + [Fact] + public void GetSession() + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + var request = UnitTestFixture.CrerateRequestMock(); + + // test execution + var session = componentHub.SessionManager.GetSession(request); + + Assert.NotNull(session); + } + + /// + /// Test the SetProperty function of the session manager. + /// + [Fact] + public void AddPropertyToSession() + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + var request = UnitTestFixture.CrerateRequestMock(); + var session = componentHub.SessionManager.GetSession(request); + + // test execution + session.SetProperty(new SessionPropertyParameter(new Parameter("test", "test param", ParameterScope.Session))); + + var testProperty = session.GetProperty(); + + Assert.NotNull(testProperty); + Assert.Single(testProperty.Params); + Assert.Equal("test param", testProperty.Params["test"].Value); + Assert.Equal(ParameterScope.Session, testProperty.Params["test"].Scope); + } + + /// + /// Test the RemoveProperty function of the session manager. + /// + [Fact] + public void RemovePropertyFromSession() + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + var request = UnitTestFixture.CrerateRequestMock(); + var session = componentHub.SessionManager.GetSession(request); + + // test execution + session.SetProperty(new SessionPropertyParameter(new Parameter("test", "test param", ParameterScope.Session))); + session.RemoveProperty(); + + var testProperty = session.GetProperty(); + + Assert.Null(testProperty); + } + } +} diff --git a/src/WebExpress.WebCore.Test/Manager/UnitTestSettingPageManager.cs b/src/WebExpress.WebCore.Test/Manager/UnitTestSettingPageManager.cs new file mode 100644 index 0000000..bab64fb --- /dev/null +++ b/src/WebExpress.WebCore.Test/Manager/UnitTestSettingPageManager.cs @@ -0,0 +1,346 @@ +using WebExpress.WebCore.Test.Fixture; +using WebExpress.WebCore.Test.WWW.Settings; +using WebExpress.WebCore.WebComponent; +using WebExpress.WebCore.WebSettingPage; + +namespace WebExpress.WebCore.Test.Manager +{ + /// + /// Test the setting page manager. + /// + [Collection("NonParallelTests")] + public class UnitTestSettingPageManager + { + /// + /// Test the register function of the setting page manager. + /// + [Fact] + public void RegisterSettingPages() + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + + // test execution + Assert.Equal(9, componentHub.SettingPageManager.SettingPages.Count()); + } + + /// + /// Test the register function of the setting page manager. + /// + [Fact] + public void RegisterSettingCategories() + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + + // test execution + Assert.Equal(9, componentHub.SettingPageManager.SettingCategories.Count()); + } + + /// + /// Test the register function of the setting page manager. + /// + [Fact] + public void RegisterSettingGroups() + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + + // test execution + Assert.Equal(9, componentHub.SettingPageManager.SettingGroups.Count()); + } + + /// + /// Test the remove function of the setting page manager. + /// + [Fact] + public void Remove() + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + var plugin = componentHub.PluginManager.GetPlugin(typeof(TestPlugin)); + var settingPageManager = componentHub.SettingPageManager as SettingPageManager; + + // test execution + settingPageManager.Remove(plugin); + + Assert.Empty(componentHub.SettingPageManager.SettingPages); + } + + /// + /// Test the id property of the setting page. + /// + [Theory] + [InlineData(typeof(TestApplicationA), typeof(TestSettingPageA), "webexpress.webcore.test.www.settings.testsettingpagea")] + [InlineData(typeof(TestApplicationA), typeof(TestSettingPageB), "webexpress.webcore.test.www.settings.testsettingpageb")] + [InlineData(typeof(TestApplicationA), typeof(TestSettingPageC), "webexpress.webcore.test.www.settings.testsettingpagec")] + [InlineData(typeof(TestApplicationB), typeof(TestSettingPageA), "webexpress.webcore.test.www.settings.testsettingpagea")] + [InlineData(typeof(TestApplicationB), typeof(TestSettingPageB), "webexpress.webcore.test.www.settings.testsettingpageb")] + [InlineData(typeof(TestApplicationB), typeof(TestSettingPageC), "webexpress.webcore.test.www.settings.testsettingpagec")] + [InlineData(typeof(TestApplicationC), typeof(TestSettingPageA), "webexpress.webcore.test.www.settings.testsettingpagea")] + [InlineData(typeof(TestApplicationC), typeof(TestSettingPageB), "webexpress.webcore.test.www.settings.testsettingpageb")] + [InlineData(typeof(TestApplicationC), typeof(TestSettingPageC), "webexpress.webcore.test.www.settings.testsettingpagec")] + public void Id(Type applicationType, Type resourceType, string id) + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + var application = componentHub.ApplicationManager.GetApplications(applicationType)?.FirstOrDefault(); + var settingPage = componentHub.SettingPageManager.GetSettingPages(resourceType, application)?.FirstOrDefault(); + + // test execution + Assert.Equal(id, settingPage.EndpointId.ToString()); + } + + /// + /// Test the title property of the setting page. + /// + [Theory] + [InlineData(typeof(TestApplicationA), typeof(TestSettingPageA), "webindex:settingpagea.label")] + [InlineData(typeof(TestApplicationA), typeof(TestSettingPageB), "webindex:settingpageb.label")] + [InlineData(typeof(TestApplicationB), typeof(TestSettingPageA), "webindex:settingpagea.label")] + [InlineData(typeof(TestApplicationB), typeof(TestSettingPageB), "webindex:settingpageb.label")] + [InlineData(typeof(TestApplicationC), typeof(TestSettingPageA), "webindex:settingpagea.label")] + [InlineData(typeof(TestApplicationC), typeof(TestSettingPageB), "webindex:settingpageb.label")] + public void Title(Type applicationType, Type resourceType, string title) + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + var application = componentHub.ApplicationManager.GetApplications(applicationType)?.FirstOrDefault(); + var settingPage = componentHub.SettingPageManager.GetSettingPages(resourceType, application)?.FirstOrDefault(); + + // test execution + Assert.Equal(title, settingPage.PageTitle); + } + + /// + /// Test the context path property of the setting page. + /// + [Theory] + [InlineData(typeof(TestApplicationA), typeof(TestSettingPageA), "/server/appa/settings/testsettingpagea")] + [InlineData(typeof(TestApplicationA), typeof(TestSettingPageB), "/server/appa/settings/testsettingpageb")] + [InlineData(typeof(TestApplicationA), typeof(TestSettingPageC), "/server/appa/settings/testsettingpagec")] + [InlineData(typeof(TestApplicationB), typeof(TestSettingPageA), "/server/appb/settings/testsettingpagea")] + [InlineData(typeof(TestApplicationB), typeof(TestSettingPageB), "/server/appb/settings/testsettingpageb")] + [InlineData(typeof(TestApplicationB), typeof(TestSettingPageC), "/server/appb/settings/testsettingpagec")] + [InlineData(typeof(TestApplicationC), typeof(TestSettingPageA), "/server/settings/testsettingpagea")] + [InlineData(typeof(TestApplicationC), typeof(TestSettingPageB), "/server/settings/testsettingpageb")] + [InlineData(typeof(TestApplicationC), typeof(TestSettingPageC), "/server/settings/testsettingpagec")] + public void RoutePath(Type applicationType, Type resourceType, string path) + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + var application = componentHub.ApplicationManager.GetApplications(applicationType)?.FirstOrDefault(); + var settingPage = componentHub.SettingPageManager.GetSettingPages(resourceType, application)?.FirstOrDefault(); + + // test execution + Assert.Equal(path, settingPage.Route.ToString()); + } + + /// + /// Tests whether the setting page manager implements interface IComponentManager. + /// + [Fact] + public void IsIComponentManager() + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + + // test execution + Assert.True(typeof(IComponentManager).IsAssignableFrom(componentHub.SettingPageManager.GetType())); + } + + /// + /// Tests whether the setting page context implements interface IContext. + /// + [Fact] + public void IsIContext() + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + + // test execution + foreach (var settingPages in componentHub.SettingPageManager.SettingPages) + { + Assert.True(typeof(IContext).IsAssignableFrom(settingPages.GetType()), $"Page context {settingPages.GetType().Name} does not implement IContext."); + } + } + + /// + /// Test the name property of the setting categories. + /// + [Theory] + [InlineData(typeof(TestApplicationA), new[] { "SettingCategory A", "SettingCategory B", "SettingCategory C" })] + [InlineData(typeof(TestApplicationB), new[] { "SettingCategory A", "SettingCategory B", "SettingCategory C" })] + [InlineData(typeof(TestApplicationC), new[] { "SettingCategory A", "SettingCategory B", "SettingCategory C" })] + public void CategoryName(Type applicationType, params string[] names) + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + var application = componentHub.ApplicationManager.GetApplications(applicationType)?.FirstOrDefault(); + var settingCategories = componentHub.SettingPageManager.GetSettingCategories(application); + + // test execution + Assert.Equal([.. names], [.. settingCategories.Select(x => x.Name)]); + } + + /// + /// Test the icon property of the setting categories. + /// + [Theory] + [InlineData(typeof(TestApplicationA), new[] { "WebExpress.WebCore.Test.TestIconBell", "WebExpress.WebCore.Test.TestIconProfile", null })] + [InlineData(typeof(TestApplicationB), new[] { "WebExpress.WebCore.Test.TestIconBell", "WebExpress.WebCore.Test.TestIconProfile", null })] + [InlineData(typeof(TestApplicationC), new[] { "WebExpress.WebCore.Test.TestIconBell", "WebExpress.WebCore.Test.TestIconProfile", null })] + public void CategoryIcon(Type applicationType, params string[] icons) + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + var application = componentHub.ApplicationManager.GetApplications(applicationType)?.FirstOrDefault(); + var settingCategories = componentHub.SettingPageManager.GetSettingCategories(application); + + // test execution + Assert.Equal([.. icons], [.. settingCategories.Select(x => x.Icon?.ToString())]); + } + + /// + /// Test the description property of the setting categories. + /// + [Theory] + [InlineData(typeof(TestApplicationA), new[] { "Description of category a.", "Description of category b.", "Description of category c." })] + [InlineData(typeof(TestApplicationB), new[] { "Description of category a.", "Description of category b.", "Description of category c." })] + [InlineData(typeof(TestApplicationC), new[] { "Description of category a.", "Description of category b.", "Description of category c." })] + public void CategoryDescription(Type applicationType, params string[] descriptions) + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + var application = componentHub.ApplicationManager.GetApplications(applicationType)?.FirstOrDefault(); + var settingCategories = componentHub.SettingPageManager.GetSettingCategories(application); + + // test execution + Assert.Equal([.. descriptions], [.. settingCategories.Select(x => x.Description)]); + } + + /// + /// Test the section property of the setting categories. + /// + [Theory] + [InlineData(typeof(TestApplicationA), new[] { SettingSection.Preferences, SettingSection.Primary, SettingSection.Secondary })] + [InlineData(typeof(TestApplicationB), new[] { SettingSection.Preferences, SettingSection.Primary, SettingSection.Secondary })] + [InlineData(typeof(TestApplicationC), new[] { SettingSection.Preferences, SettingSection.Primary, SettingSection.Secondary })] + public void CategorySection(Type applicationType, params SettingSection[] sections) + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + var application = componentHub.ApplicationManager.GetApplications(applicationType)?.FirstOrDefault(); + var settingCategories = componentHub.SettingPageManager.GetSettingCategories(application); + + // test execution + Assert.Equal([.. sections], [.. settingCategories.Select(x => x.Section)]); + } + + /// + /// Test the name property of the setting groups. + /// + [Theory] + [InlineData(typeof(TestApplicationA), typeof(TestSettingCategoryA), new[] { "SettingGroup A", "SettingGroup B" })] + [InlineData(typeof(TestApplicationB), typeof(TestSettingCategoryA), new[] { "SettingGroup A", "SettingGroup B" })] + [InlineData(typeof(TestApplicationC), typeof(TestSettingCategoryA), new[] { "SettingGroup A", "SettingGroup B" })] + [InlineData(typeof(TestApplicationA), typeof(TestSettingCategoryB), new string[0])] + [InlineData(typeof(TestApplicationA), null, new[] { "SettingGroup C" })] + public void GroupName(Type applicationType, Type settingCategoryType, params string[] names) + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + var application = componentHub.ApplicationManager.GetApplications(applicationType)?.FirstOrDefault(); + var settingCategory = componentHub.SettingPageManager.GetSettingCategories(application).FirstOrDefault(x => x.CategoryId.ToString() == settingCategoryType?.FullName.ToLower()); + var settinGroups = componentHub.SettingPageManager.GetSettingGroups(application, settingCategory); + + // test execution + Assert.Equal([.. names], [.. settinGroups.Select(x => x.Name)]); + } + + /// + /// Test the description property of the setting groups. + /// + [Theory] + [InlineData(typeof(TestApplicationA), typeof(TestSettingCategoryA), new[] { "Description of group a.", "Description of group b." })] + [InlineData(typeof(TestApplicationB), typeof(TestSettingCategoryA), new[] { "Description of group a.", "Description of group b." })] + [InlineData(typeof(TestApplicationC), typeof(TestSettingCategoryA), new[] { "Description of group a.", "Description of group b." })] + [InlineData(typeof(TestApplicationA), typeof(TestSettingCategoryB), new string[0])] + [InlineData(typeof(TestApplicationA), null, new[] { "Description of group c." })] + public void GroupDescription(Type applicationType, Type settingCategoryType, params string[] descriptions) + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + var application = componentHub.ApplicationManager.GetApplications(applicationType)?.FirstOrDefault(); + var settingCategory = componentHub.SettingPageManager.GetSettingCategories(application).FirstOrDefault(x => x.CategoryId.ToString() == settingCategoryType?.FullName.ToLower()); + var settinGroups = componentHub.SettingPageManager.GetSettingGroups(application, settingCategory); + + // test execution + Assert.Equal([.. descriptions], [.. settinGroups.Select(x => x.Description)]); + } + + /// + /// Test the section property of the setting groups. + /// + [Theory] + [InlineData(typeof(TestApplicationA), typeof(TestSettingCategoryA), new[] { SettingSection.Preferences, SettingSection.Primary })] + [InlineData(typeof(TestApplicationB), typeof(TestSettingCategoryA), new[] { SettingSection.Preferences, SettingSection.Primary })] + [InlineData(typeof(TestApplicationC), typeof(TestSettingCategoryA), new[] { SettingSection.Preferences, SettingSection.Primary })] + [InlineData(typeof(TestApplicationA), typeof(TestSettingCategoryB), new SettingSection[0])] + [InlineData(typeof(TestApplicationA), null, new[] { SettingSection.Secondary })] + public void GroupSection(Type applicationType, Type settingCategoryType, params SettingSection[] sections) + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + var application = componentHub.ApplicationManager.GetApplications(applicationType)?.FirstOrDefault(); + var settingCategory = componentHub.SettingPageManager.GetSettingCategories(application).FirstOrDefault(x => x.CategoryId.ToString() == settingCategoryType?.FullName.ToLower()); + var settinGroups = componentHub.SettingPageManager.GetSettingGroups(application, settingCategory); + + // test execution + Assert.Equal([.. sections], [.. settinGroups.Select(x => x.Section)]); + } + + /// + /// Test the category property of the setting groups. + /// + [Theory] + [InlineData(typeof(TestApplicationA), typeof(TestSettingCategoryA))] + [InlineData(typeof(TestApplicationB), typeof(TestSettingCategoryA))] + [InlineData(typeof(TestApplicationC), typeof(TestSettingCategoryA))] + [InlineData(typeof(TestApplicationA), typeof(TestSettingCategoryB))] + [InlineData(typeof(TestApplicationA), null)] + public void GroupCategory(Type applicationType, Type settingCategoryType) + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + var application = componentHub.ApplicationManager.GetApplications(applicationType)?.FirstOrDefault(); + var settingCategory = componentHub.SettingPageManager.GetSettingCategories(application).FirstOrDefault(x => x.CategoryId.ToString() == settingCategoryType?.FullName.ToLower()); + var settinGroups = componentHub.SettingPageManager.GetSettingGroups(application, settingCategory); + + // test execution + Assert.Equal(settinGroups.Count(), settinGroups.Where(x => x.SettingCategory == settingCategory).Count()); + } + + /// + /// Test the GetFirstSettingPage function of the setting manager. + /// + [Theory] + [InlineData(typeof(TestApplicationA), typeof(TestSettingCategoryA), typeof(TestSettingPageA))] + [InlineData(typeof(TestApplicationB), typeof(TestSettingCategoryA), typeof(TestSettingPageA))] + [InlineData(typeof(TestApplicationC), typeof(TestSettingCategoryA), typeof(TestSettingPageA))] + [InlineData(typeof(TestApplicationA), typeof(TestSettingCategoryB), null)] + [InlineData(typeof(TestApplicationA), null, typeof(TestSettingPageC))] + public void GetFirstSettingPage(Type applicationType, Type settingCategoryType, Type firstPageType) + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + var application = componentHub.ApplicationManager.GetApplications(applicationType)?.FirstOrDefault(); + var settingCategory = componentHub.SettingPageManager.GetSettingCategories(application).FirstOrDefault(x => x.CategoryId.ToString() == settingCategoryType?.FullName.ToLower()); + var firstPage = firstPageType != null ? componentHub.SettingPageManager.GetSettingPages(firstPageType, application).FirstOrDefault() : null; + var settingPage = componentHub.SettingPageManager.GetFirstSettingPage(application, settingCategory); + + // test execution + Assert.Equal(firstPage, settingPage); + } + } +} diff --git a/src/WebExpress.WebCore.Test/Manager/UnitTestSitemapManager.cs b/src/WebExpress.WebCore.Test/Manager/UnitTestSitemapManager.cs new file mode 100644 index 0000000..bd10883 --- /dev/null +++ b/src/WebExpress.WebCore.Test/Manager/UnitTestSitemapManager.cs @@ -0,0 +1,188 @@ +using WebExpress.WebCore.Test.Fixture; +using WebExpress.WebCore.Test.WWW.Api._1; +using WebExpress.WebCore.Test.WWW.Api._2; +using WebExpress.WebCore.Test.WWW.Api._3; +using WebExpress.WebCore.Test.WWW.Resources; +using WebExpress.WebCore.WebComponent; +using WebExpress.WebCore.WebSitemap; +using WebExpress.WebCore.WebUri; + +namespace WebExpress.WebCore.Test.Manager +{ + /// + /// Test the sitemap manager. + /// + [Collection("NonParallelTests")] + public class UnitTestSitemapManager + { + /// + /// Test the refresh function of the sitemap manager. + /// + [Fact] + public void Refresh() + { + // preconditions + var componentManager = UnitTestFixture.CreateAndRegisterComponentHubMock(); + + // test execution + componentManager.SitemapManager.Refresh(); + + Assert.Equal(79, componentManager.SitemapManager.SiteMap.Count()); + } + + /// + /// Test the search resource function of the sitemap. + /// + [Theory] + [InlineData("http://localhost:8080/server/appa/resources/testresourcea", "webexpress.webcore.test.www.resources.testresourcea")] + [InlineData("http://localhost:8080/server/appa/resources/testresourceb", "webexpress.webcore.test.www.resources.testresourceb")] + [InlineData("http://localhost:8080/server/appa/resources/testresourcec", "webexpress.webcore.test.www.resources.testresourcec")] + [InlineData("http://localhost:8080/server/appa/resources/testresourced", "webexpress.webcore.test.www.resources.testresourced")] + [InlineData("http://localhost:8080/server/appb/resources/testresourcea", "webexpress.webcore.test.www.resources.testresourcea")] + [InlineData("http://localhost:8080/server/appb/resources/testresourceb", "webexpress.webcore.test.www.resources.testresourceb")] + [InlineData("http://localhost:8080/server/appb/resources/testresourcec", "webexpress.webcore.test.www.resources.testresourcec")] + [InlineData("http://localhost:8080/server/appb/resources/testresourced", "webexpress.webcore.test.www.resources.testresourced")] + [InlineData("http://localhost:8080/server/resources/testresourcea", "webexpress.webcore.test.www.resources.testresourcea")] + [InlineData("http://localhost:8080/server/resources/testresourceb", "webexpress.webcore.test.www.resources.testresourceb")] + [InlineData("http://localhost:8080/server/resources/testresourcec", "webexpress.webcore.test.www.resources.testresourcec")] + [InlineData("http://localhost:8080/server/resources/testresourced", "webexpress.webcore.test.www.resources.testresourced")] + [InlineData("http://localhost:8080/server/appa", "webexpress.webcore.test.www.index")] + [InlineData("http://localhost:8080/server/appa/about", "webexpress.webcore.test.www.about")] + [InlineData("http://localhost:8080/server/appa/contact", "webexpress.webcore.test.www.contact")] + [InlineData("http://localhost:8080/server/appb", "webexpress.webcore.test.www.index")] + [InlineData("http://localhost:8080/server/appb/about", "webexpress.webcore.test.www.about")] + [InlineData("http://localhost:8080/server/appb/contact", "webexpress.webcore.test.www.contact")] + [InlineData("http://localhost:8080/server", "webexpress.webcore.test.www.index")] + [InlineData("http://localhost:8080/server/about", "webexpress.webcore.test.www.about")] + [InlineData("http://localhost:8080/server/contact", "webexpress.webcore.test.www.contact")] + [InlineData("http://localhost:8080/server/appa/api/1/testrestapia", "webexpress.webcore.test.www.api._1.testrestapia")] + [InlineData("http://localhost:8080/server/appa/api/2/testrestapib", "webexpress.webcore.test.www.api._2.testrestapib")] + [InlineData("http://localhost:8080/server/appa/api/3/testrestapic", "webexpress.webcore.test.www.api._3.testrestapic")] + [InlineData("http://localhost:8080/server/appa/assets/css/mycss.css", "webexpress.webcore.asset")] + [InlineData("http://localhost:8080/server/appa/assets/js/myjavascript.js", "webexpress.webcore.asset")] + [InlineData("http://localhost:8080/server/appa/assets/js/myjavascript.mini.js", "webexpress.webcore.asset")] + [InlineData("http://localhost:8080/server/appa/assets/css.mycss.css", "webexpress.webcore.asset")] + [InlineData("http://localhost:8080/server/appa/assets/js.myjavascript.js", "webexpress.webcore.asset")] + [InlineData("http://localhost:8080/server/appa/assets/js.myjavascript.mini.js", "webexpress.webcore.asset")] + [InlineData("http://localhost:8080/uri/does/not/exist", null)] + public void SearchResource(string uri, string id) + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + var context = UnitTestFixture.CreateHttpContextMock(); + var httpServerContext = UnitTestFixture.CreateHttpServerContextMock(); + componentHub.SitemapManager.Refresh(); + + // test execution + var searchResult = componentHub.SitemapManager.SearchResource(new System.Uri(uri), new SearchContext() + { + HttpServerContext = httpServerContext, + Culture = httpServerContext.Culture, + HttpContext = context + }); + + componentHub.EndpointManager.HandleRequest(UnitTestFixture.CrerateRequestMock(), searchResult?.EndpointContext); + + Assert.Equal(id, searchResult?.EndpointContext?.EndpointId.ToString()); + } + + /// + /// Test the get uri function of the sitemap. + /// + [Theory] + [InlineData(typeof(TestApplicationA), typeof(TestResourceA), null, "/server/appa/resources/testresourcea")] + [InlineData(typeof(TestApplicationA), typeof(TestResourceB), null, "/server/appa/resources/testresourceb")] + [InlineData(typeof(TestApplicationA), typeof(TestResourceC), null, "/server/appa/resources/testresourcec")] + [InlineData(typeof(TestApplicationA), typeof(TestResourceD), null, "/server/appa/resources/testresourced")] + [InlineData(typeof(TestApplicationA), typeof(TestRestApiA), null, "/server/appa/api/1/testrestapia")] + [InlineData(typeof(TestApplicationA), typeof(TestRestApiB), null, "/server/appa/api/2/testrestapib")] + [InlineData(typeof(TestApplicationA), typeof(TestRestApiC), null, "/server/appa/api/3/testrestapic")] + [InlineData(typeof(TestApplicationA), typeof(WWW.Index), null, "/server/appa")] + [InlineData(typeof(TestApplicationA), typeof(WWW.About), null, "/server/appa/about")] + [InlineData(typeof(TestApplicationA), typeof(WWW.Contact), null, "/server/appa/contact")] + [InlineData(typeof(TestApplicationA), typeof(WWW.Blog.Index), null, "/server/appa/blog")] + [InlineData(typeof(TestApplicationA), typeof(WWW.Blog.Post.Index), null, "/server/appa/blog/post")] + [InlineData(typeof(TestApplicationA), typeof(WWW.Blog.Post.Add), null, "/server/appa/blog/post/add")] + [InlineData(typeof(TestApplicationA), typeof(WWW.Blog.Post.PostId.Index), null, "/server/appa/blog/post/${testparametera}")] + [InlineData(typeof(TestApplicationA), typeof(WWW.Blog.Post.PostId.Edit), null, "/server/appa/blog/post/${testparametera}/edit")] + [InlineData(typeof(TestApplicationA), typeof(WWW.Blog.Post.PostId.Index), 1, "/server/appa/blog/post/1")] + [InlineData(typeof(TestApplicationA), typeof(WWW.Blog.Post.PostId.Edit), 1, "/server/appa/blog/post/1/edit")] + [InlineData(typeof(TestApplicationA), typeof(WWW.Products.Index), null, "/server/appa/products")] + [InlineData(typeof(TestApplicationA), typeof(WWW.Products.List), null, "/server/appa/products/list")] + [InlineData(typeof(TestApplicationA), typeof(WWW.Products.Details.Index), null, "/server/appa/products/${testparametera}")] + [InlineData(typeof(TestApplicationA), typeof(WWW.Products.Details.Index), 2, "/server/appa/products/2")] + public void GetUri(Type applicationType, Type resourceType, int? param, string expected) + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + var application = componentHub.ApplicationManager.GetApplications(applicationType)?.FirstOrDefault(); + componentHub.SitemapManager.Refresh(); + + // test execution + var uri = componentHub.SitemapManager.GetUri(resourceType, application, [param.HasValue ? new TestParameterA(param.Value) : null]); + + Assert.Equal(expected, uri?.ToString()); + } + + /// + /// Test the get endpoint function of the sitemap. + /// + [Theory] + [InlineData("http://localhost:8080/uri/does/not/exist", null)] + [InlineData("http://localhost:8080/server/appa/resources/testresourcea", "webexpress.webcore.test.www.resources.testresourcea")] + [InlineData("http://localhost:8080/server/appa/resources/testresourceb", "webexpress.webcore.test.www.resources.testresourceb")] + [InlineData("http://localhost:8080/server/appa/resources/testresourcec", "webexpress.webcore.test.www.resources.testresourcec")] + [InlineData("http://localhost:8080/server/appa/resources/testresourced", "webexpress.webcore.test.www.resources.testresourced")] + [InlineData("http://localhost:8080/server/appa/assets/css/mycss.css", "webexpress.webcore.asset")] + [InlineData("http://localhost:8080/server/appa/assets/js/myjavascript.js", "webexpress.webcore.asset")] + [InlineData("http://localhost:8080/server/appa/assets/js/myjavascript.mini.js", "webexpress.webcore.asset")] + [InlineData("http://localhost:8080/server/appa/assets/css.mycss.css", "webexpress.webcore.asset")] + [InlineData("http://localhost:8080/server/appa/assets/js.myjavascript.js", "webexpress.webcore.asset")] + [InlineData("http://localhost:8080/server/appa/assets/js.myjavascript.mini.js", "webexpress.webcore.asset")] + [InlineData("http://localhost:8080/server/appa/api/1/testrestapia", "webexpress.webcore.test.www.api._1.testrestapia")] + [InlineData("http://localhost:8080/server/appa/api/2/TestRestApiB", "webexpress.webcore.test.www.api._2.testrestapib")] + [InlineData("http://localhost:8080/server/appa/api/3/testrestapic", "webexpress.webcore.test.www.api._3.testrestapic")] + [InlineData("http://localhost:8080/server/appa", "webexpress.webcore.test.www.index")] + [InlineData("http://localhost:8080/server/appa/", "webexpress.webcore.test.www.index")] + [InlineData("http://localhost:8080/server/appa/about", "webexpress.webcore.test.www.about")] + [InlineData("http://localhost:8080/server/appa/About", "webexpress.webcore.test.www.about")] + [InlineData("http://localhost:8080/server/appa/about/", "webexpress.webcore.test.www.about")] + [InlineData("http://localhost:8080/server/appa/About/", "webexpress.webcore.test.www.about")] + [InlineData("http://localhost:8080/server/appa/contact", "webexpress.webcore.test.www.contact")] + [InlineData("http://localhost:8080/server/appa/blog", "webexpress.webcore.test.www.blog.index")] + [InlineData("http://localhost:8080/server/appa/blog/post", "webexpress.webcore.test.www.blog.post.index")] + [InlineData("http://localhost:8080/server/appa/blog/post/add", "webexpress.webcore.test.www.blog.post.add")] + [InlineData("http://localhost:8080/server/appa/blog/post/10E96737-5C72-4C25-9E74-F96D8863D123/edit", "webexpress.webcore.test.www.blog.post.postid.edit")] + [InlineData("http://localhost:8080/server/appa/blog/post/10E96737-5C72-4C25-9E74-F96D8863D123", "webexpress.webcore.test.www.blog.post.postid.index")] + [InlineData("http://localhost:8080/server/appa/blog/post/10E96737-5C72-4C25-9E74-F96D8863D123/", "webexpress.webcore.test.www.blog.post.postid.index")] + [InlineData("http://localhost:8080/server/appa/Products", "webexpress.webcore.test.www.products.index")] + [InlineData("http://localhost:8080/server/appa/Products/", "webexpress.webcore.test.www.products.index")] + [InlineData("http://localhost:8080/server/appa/Products/list", "webexpress.webcore.test.www.products.list")] + [InlineData("http://localhost:8080/server/appa/products/10E96737-5C72-4C25-9E74-F96D8863D123", "webexpress.webcore.test.www.products.details.index")] + [InlineData("http://localhost:8080/server/appa/products/10E96737-5C72-4C25-9E74-F96D8863D123/", "webexpress.webcore.test.www.products.details.index")] + public void GetEndpoint(string uri, string expected) + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + componentHub.SitemapManager.Refresh(); + + // test execution + var endpoint = componentHub.SitemapManager.GetEndpoint(new UriEndpoint(uri)); + + Assert.Equal(expected, endpoint?.EndpointId?.ToString()); + } + + /// + /// Tests whether the sitemap manager implements interface IComponentManager. + /// + [Fact] + public void IsIComponentManager() + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + + // test execution + Assert.True(typeof(IComponentManager).IsAssignableFrom(componentHub.SitemapManager.GetType())); + } + } +} diff --git a/src/WebExpress.WebCore.Test/Manager/UnitTestStatusPageManager.cs b/src/WebExpress.WebCore.Test/Manager/UnitTestStatusPageManager.cs new file mode 100644 index 0000000..56ded64 --- /dev/null +++ b/src/WebExpress.WebCore.Test/Manager/UnitTestStatusPageManager.cs @@ -0,0 +1,211 @@ +using WebExpress.WebCore.Test.Fixture; +using WebExpress.WebCore.WebComponent; +using WebExpress.WebCore.WebStatusPage; + +namespace WebExpress.WebCore.Test.Manager +{ + /// + /// Test the status page manager. + /// + [Collection("NonParallelTests")] + public class UnitTestStatusPageManager + { + /// + /// Test the register function of the status page manager. + /// + [Fact] + public void Register() + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + + // test execution + Assert.Equal(12, componentHub.StatusPageManager.StatusPages.Count()); + } + + /// + /// Test the remove function of the status page manager. + /// + [Fact] + public void Remove() + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + var plugin = componentHub.PluginManager.GetPlugin(typeof(TestPlugin)); + var statusPageManager = componentHub.StatusPageManager as StatusPageManager; + + // test execution + statusPageManager.Remove(plugin); + + Assert.Empty(componentHub.StatusPageManager.StatusPages); + } + + /// + /// Test the id property of the status page. + /// + [Theory] + [InlineData(typeof(TestApplicationA), typeof(TestStatusPage301), "webexpress.webcore.test.teststatuspage301")] + [InlineData(typeof(TestApplicationA), typeof(TestStatusPage400), "webexpress.webcore.test.teststatuspage400")] + [InlineData(typeof(TestApplicationA), typeof(TestStatusPage404), "webexpress.webcore.test.teststatuspage404")] + [InlineData(typeof(TestApplicationA), typeof(TestStatusPage500), "webexpress.webcore.test.teststatuspage500")] + + public void Id(Type applicationType, Type statusPageType, string id) + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + var application = componentHub.ApplicationManager.GetApplications(applicationType).FirstOrDefault(); + var statusPage = componentHub.StatusPageManager.GetStatusPage(application, statusPageType); + + // test execution + Assert.Equal(id, statusPage.StatusPageId.ToString()); + } + + /// + /// Test the title property of the status page. + /// + [Theory] + [InlineData(typeof(TestApplicationA), typeof(TestStatusPage301), "webindex:homepage.label")] + [InlineData(typeof(TestApplicationA), typeof(TestStatusPage400), "webindex:homepage.label")] + [InlineData(typeof(TestApplicationA), typeof(TestStatusPage404), "webindex:homepage.label")] + [InlineData(typeof(TestApplicationA), typeof(TestStatusPage500), "webindex:homepage.label")] + [InlineData(typeof(TestApplicationB), typeof(TestStatusPage301), "webindex:homepage.label")] + [InlineData(typeof(TestApplicationB), typeof(TestStatusPage400), "webindex:homepage.label")] + [InlineData(typeof(TestApplicationB), typeof(TestStatusPage404), "webindex:homepage.label")] + [InlineData(typeof(TestApplicationB), typeof(TestStatusPage500), "webindex:homepage.label")] + [InlineData(typeof(TestApplicationC), typeof(TestStatusPage301), "webindex:homepage.label")] + [InlineData(typeof(TestApplicationC), typeof(TestStatusPage400), "webindex:homepage.label")] + [InlineData(typeof(TestApplicationC), typeof(TestStatusPage404), "webindex:homepage.label")] + [InlineData(typeof(TestApplicationC), typeof(TestStatusPage500), "webindex:homepage.label")] + public void Title(Type applicationType, Type resourceType, string title) + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + var application = componentHub.ApplicationManager.GetApplications(applicationType).FirstOrDefault(); + var statusPage = componentHub.StatusPageManager.GetStatusPage(application, resourceType); + + // test execution + Assert.Equal(title, statusPage.StatusTitle); + } + + /// + /// Test the id property of the status page. + /// + [Theory] + [InlineData(typeof(TestApplicationA), typeof(TestStatusPage301), 301)] + [InlineData(typeof(TestApplicationA), typeof(TestStatusPage400), 400)] + [InlineData(typeof(TestApplicationA), typeof(TestStatusPage404), 404)] + [InlineData(typeof(TestApplicationA), typeof(TestStatusPage500), 500)] + [InlineData(typeof(TestApplicationB), typeof(TestStatusPage301), 301)] + [InlineData(typeof(TestApplicationB), typeof(TestStatusPage400), 400)] + [InlineData(typeof(TestApplicationB), typeof(TestStatusPage404), 404)] + [InlineData(typeof(TestApplicationB), typeof(TestStatusPage500), 500)] + [InlineData(typeof(TestApplicationC), typeof(TestStatusPage301), 301)] + [InlineData(typeof(TestApplicationC), typeof(TestStatusPage400), 400)] + [InlineData(typeof(TestApplicationC), typeof(TestStatusPage404), 404)] + [InlineData(typeof(TestApplicationC), typeof(TestStatusPage500), 500)] + public void Code(Type applicationType, Type statusPageType, int? code) + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + var application = componentHub.ApplicationManager.GetApplications(applicationType).FirstOrDefault(); + var statusPage = componentHub.StatusPageManager.GetStatusPage(application, statusPageType); + + // test execution + Assert.Equal(code, statusPage?.StatusCode); + } + + /// + /// Test the icon property of the status page. + /// + [Theory] + [InlineData(typeof(TestApplicationA), typeof(TestStatusPage301), null)] + [InlineData(typeof(TestApplicationA), typeof(TestStatusPage400), "/server/appa/webexpress/icon.png")] + [InlineData(typeof(TestApplicationA), typeof(TestStatusPage404), "/server/appa/webexpress/icon.png")] + [InlineData(typeof(TestApplicationA), typeof(TestStatusPage500), "/server/appa/webexpress/icon.png")] + [InlineData(typeof(TestApplicationB), typeof(TestStatusPage301), null)] + [InlineData(typeof(TestApplicationB), typeof(TestStatusPage400), "/server/appb/webexpress/icon.png")] + [InlineData(typeof(TestApplicationB), typeof(TestStatusPage404), "/server/appb/webexpress/icon.png")] + [InlineData(typeof(TestApplicationB), typeof(TestStatusPage500), "/server/appb/webexpress/icon.png")] + [InlineData(typeof(TestApplicationC), typeof(TestStatusPage301), null)] + [InlineData(typeof(TestApplicationC), typeof(TestStatusPage400), "/server/webexpress/icon.png")] + [InlineData(typeof(TestApplicationC), typeof(TestStatusPage404), "/server/webexpress/icon.png")] + [InlineData(typeof(TestApplicationC), typeof(TestStatusPage500), "/server/webexpress/icon.png")] + public void Icon(Type applicationType, Type statusPageType, string icon) + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + var application = componentHub.ApplicationManager.GetApplications(applicationType).FirstOrDefault(); + var statusPage = componentHub.StatusPageManager.GetStatusPage(application, statusPageType); + + // test execution + Assert.Equal(icon, statusPage?.StatusIcon?.ToString()); + } + + /// + /// Test the CreateStatusResponse function of the status page. + /// + [Theory] + [InlineData(typeof(TestApplicationA), 400, 400)] + [InlineData(typeof(TestApplicationA), 404, 404)] + [InlineData(typeof(TestApplicationB), 404, 404)] + [InlineData(typeof(TestApplicationB), 500, 500)] + [InlineData(typeof(TestApplicationA), 500, 500)] + public void CreateAndCheckCode(Type applicationType, int statusCode, int? expected) + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + var application = componentHub.ApplicationManager.GetApplications(applicationType).FirstOrDefault(); + var statusResponse = componentHub.StatusPageManager.CreateStatusResponse("content", statusCode, application, UnitTestFixture.CreateHttpContextMock().Request); + + // test execution + Assert.Equal(expected, statusResponse?.Status); + } + + /// + /// Test the CreateStatusResponse function of the status page. + /// + [Theory] + [InlineData(typeof(TestApplicationA), 400, "content", "content", 78)] + [InlineData(typeof(TestApplicationA), 500, "content", "content", 78)] + public void CreateAndCheckMessage(Type applicationType, int statusCode, string content, string expected, int length) + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + var application = componentHub.ApplicationManager.GetApplications(applicationType).FirstOrDefault(); + var statusResponse = componentHub.StatusPageManager.CreateStatusResponse(content, statusCode, application, UnitTestFixture.CreateHttpContextMock().Request); + + // test execution + Assert.Contains(expected, statusResponse?.Content?.ToString()); + Assert.Equal(length, statusResponse?.Header?.ContentLength); + } + + /// + /// Tests whether the status page manager implements interface IComponentManager. + /// + [Fact] + public void IsIComponentManager() + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + + // test execution + Assert.True(typeof(IComponentManager).IsAssignableFrom(componentHub.StatusPageManager.GetType())); + } + + /// + /// Tests whether the status page context implements interface IContext. + /// + [Fact] + public void IsIContext() + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + + // test execution + foreach (var application in componentHub.StatusPageManager.StatusPages) + { + Assert.True(typeof(IContext).IsAssignableFrom(application.GetType()), $"Page context {application.GetType().Name} does not implement IContext."); + } + } + } +} diff --git a/src/WebExpress.WebCore.Test/Manager/UnitTestTaskManager.cs b/src/WebExpress.WebCore.Test/Manager/UnitTestTaskManager.cs new file mode 100644 index 0000000..cc5b0db --- /dev/null +++ b/src/WebExpress.WebCore.Test/Manager/UnitTestTaskManager.cs @@ -0,0 +1,116 @@ +using WebExpress.WebCore.Test.Fixture; +using WebExpress.WebCore.WebComponent; + +namespace WebExpress.WebCore.Test.Manager +{ + /// + /// Test the task manager. + /// + [Collection("NonParallelTests")] + public class UnitTestTaskManager + { + /// + /// Tests whether the task manager implements interface IComponentManager. + /// + [Fact] + public void IsIComponentManager() + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + + // test execution + Assert.True(typeof(IComponentManager).IsAssignableFrom(componentHub.ResourceManager.GetType())); + } + + /// + /// Tests whether the task context implements interface IComponent. + /// + [Fact] + public void IsCompopnent() + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + + componentHub.TaskManager.CreateTask("test"); + + // test execution + foreach (var task in componentHub.TaskManager.Tasks) + { + Assert.True(typeof(IComponent).IsAssignableFrom(task.GetType()), $"Task {task.GetType().Name} does not implement IComponent."); + } + } + + /// + /// Test the create task of the task manager. + /// + [Fact] + public void CreateSystemTask() + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + + // test execution + var task = componentHub.TaskManager.CreateTask("test"); + Assert.Equal("test", task?.Id); + } + + /// + /// Test the create task of the task manager. + /// + [Fact] + public void CreateOwnTask() + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + + // test execution + var task = componentHub.TaskManager.CreateTask("test", null, []); + Assert.Equal("test", task?.Id); + } + + /// + /// Test the contains task function of the task manager. + /// + [Fact] + public void ContainsTask() + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + var task = componentHub.TaskManager.CreateTask("test"); + + // test execution + var res = componentHub.TaskManager.ContainsTask("test"); + Assert.True(res); + } + + /// + /// Test the get task function of the task manager. + /// + [Fact] + public void GetTask() + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + var task = componentHub.TaskManager.CreateTask("test"); + + // test execution + var res = componentHub.TaskManager.GetTask("test"); + Assert.Equal(task, res); + } + + /// + /// Test the remove task function of the task manager. + /// + [Fact] + public void RemoveTask() + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + var task = componentHub.TaskManager.CreateTask("test"); + + // test execution + componentHub.TaskManager.RemoveTask(task); + Assert.Empty(componentHub.TaskManager.Tasks); + } + } +} diff --git a/src/WebExpress.WebCore.Test/Manager/UnitTestThemeManager.cs b/src/WebExpress.WebCore.Test/Manager/UnitTestThemeManager.cs new file mode 100644 index 0000000..d7300d8 --- /dev/null +++ b/src/WebExpress.WebCore.Test/Manager/UnitTestThemeManager.cs @@ -0,0 +1,204 @@ +using WebExpress.WebCore.Test.Fixture; +using WebExpress.WebCore.WebComponent; +using WebExpress.WebCore.WebTheme; + +namespace WebExpress.WebCore.Test.Manager +{ + /// + /// Test the theme manager. + /// + [Collection("NonParallelTests")] + public class UnitTestThemeManager + { + /// + /// Test the register function of the theme manager. + /// + [Fact] + public void Register() + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + + // test execution + Assert.Equal(6, componentHub.ThemeManager.Themes.Count()); + } + + /// + /// Test the remove function of the theme manager. + /// + [Fact] + public void Remove() + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + var themeManager = componentHub.ThemeManager as ThemeManager; + var plugin = componentHub.PluginManager.GetPlugin(typeof(TestPlugin)); + + // test execution + themeManager.Remove(plugin); + + Assert.Empty(componentHub.ThemeManager.Themes); + } + + /// + /// Tests whether the theme manager implements interface IComponentManager. + /// + [Fact] + public void IsIComponentManager() + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + + // test execution + Assert.True(typeof(IComponentManager).IsAssignableFrom(componentHub.ThemeManager.GetType())); + } + + /// + /// Test the id property of the theme. + /// + [Theory] + [InlineData(typeof(TestApplicationA), typeof(TestThemeA), "webexpress.webcore.test.testthemea")] + [InlineData(typeof(TestApplicationA), typeof(TestThemeB), "webexpress.webcore.test.testthemeb")] + [InlineData(typeof(TestApplicationB), typeof(TestThemeA), "webexpress.webcore.test.testthemea")] + [InlineData(typeof(TestApplicationB), typeof(TestThemeB), "webexpress.webcore.test.testthemeb")] + [InlineData(typeof(TestApplicationC), typeof(TestThemeA), "webexpress.webcore.test.testthemea")] + [InlineData(typeof(TestApplicationC), typeof(TestThemeB), "webexpress.webcore.test.testthemeb")] + public void Id(Type applicationType, Type themeType, string id) + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + var application = componentHub.ApplicationManager.GetApplications(applicationType).FirstOrDefault(); + + // test execution + var themes = componentHub.ThemeManager.GetThemes(application, themeType); + + if (id == null) + { + Assert.Empty(themes); + return; + } + + Assert.Contains(id, themes.Select(x => x.ThemeId?.ToString())); + } + + /// + /// Test the name property of the theme. + /// + [Theory] + [InlineData(typeof(TestApplicationA), typeof(TestThemeA), "TestThemeA")] + [InlineData(typeof(TestApplicationA), typeof(TestThemeB), "TestThemeB")] + [InlineData(typeof(TestApplicationB), typeof(TestThemeA), "TestThemeA")] + [InlineData(typeof(TestApplicationB), typeof(TestThemeB), "TestThemeB")] + [InlineData(typeof(TestApplicationC), typeof(TestThemeA), "TestThemeA")] + [InlineData(typeof(TestApplicationC), typeof(TestThemeB), "TestThemeB")] + public void Name(Type applicationType, Type themeType, string name) + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + var application = componentHub.ApplicationManager.GetApplications(applicationType).FirstOrDefault(); + + // test execution + var themes = componentHub.ThemeManager.GetThemes(application, themeType); + + if (name == null) + { + Assert.Empty(themes); + return; + } + + Assert.Contains(name, themes?.Select(x => x.Name)); + } + + /// + /// Test the description property of the theme. + /// + [Theory] + [InlineData(typeof(TestApplicationA), typeof(TestThemeA), "A dummy theme for testing.")] + [InlineData(typeof(TestApplicationA), typeof(TestThemeB), null)] + [InlineData(typeof(TestApplicationB), typeof(TestThemeA), "A dummy theme for testing.")] + [InlineData(typeof(TestApplicationB), typeof(TestThemeB), null)] + [InlineData(typeof(TestApplicationC), typeof(TestThemeA), "A dummy theme for testing.")] + [InlineData(typeof(TestApplicationC), typeof(TestThemeB), null)] + public void Description(Type applicationType, Type themeType, string expected) + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + var application = componentHub.ApplicationManager.GetApplications(applicationType).FirstOrDefault(); + + // test execution + var theme = componentHub.ThemeManager.GetThemes(application, themeType).FirstOrDefault(); + + Assert.NotNull(theme); + Assert.Equal(expected, theme?.Description); + } + + /// + /// Test the image property of the theme. + /// + [Theory] + [InlineData(typeof(TestApplicationA), typeof(TestThemeA), "/server/appa/webexpress.webcore.test.testthemea.png")] + [InlineData(typeof(TestApplicationA), typeof(TestThemeB), null)] + [InlineData(typeof(TestApplicationB), typeof(TestThemeA), "/server/appb/webexpress.webcore.test.testthemea.png")] + [InlineData(typeof(TestApplicationB), typeof(TestThemeB), null)] + [InlineData(typeof(TestApplicationC), typeof(TestThemeA), "/server/webexpress.webcore.test.testthemea.png")] + [InlineData(typeof(TestApplicationC), typeof(TestThemeB), null)] + public void Image(Type applicationType, Type themeType, string expected) + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + var application = componentHub.ApplicationManager.GetApplications(applicationType).FirstOrDefault(); + + // test execution + var theme = componentHub.ThemeManager.GetThemes(application, themeType).FirstOrDefault(); + + Assert.NotNull(theme); + Assert.Equal(expected, theme?.Image?.ToString()); + } + + /// + /// Test the theme mode property of the theme. + /// + [Theory] + [InlineData(typeof(TestApplicationA), typeof(TestThemeA), ThemeMode.Dark)] + [InlineData(typeof(TestApplicationA), typeof(TestThemeB), ThemeMode.Light)] + [InlineData(typeof(TestApplicationB), typeof(TestThemeA), ThemeMode.Dark)] + [InlineData(typeof(TestApplicationB), typeof(TestThemeB), ThemeMode.Light)] + [InlineData(typeof(TestApplicationC), typeof(TestThemeA), ThemeMode.Dark)] + [InlineData(typeof(TestApplicationC), typeof(TestThemeB), ThemeMode.Light)] + public void Mode(Type applicationType, Type themeType, ThemeMode expected) + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + var application = componentHub.ApplicationManager.GetApplications(applicationType).FirstOrDefault(); + + // test execution + var theme = componentHub.ThemeManager.GetThemes(application, themeType).FirstOrDefault(); + + Assert.NotNull(theme); + Assert.Equal(expected, theme?.ThemeMode); + } + + /// + /// Test the theme style property of the theme. + /// + [Theory] + [InlineData(typeof(TestApplicationA), typeof(TestThemeA), "/server/appa/asserts/css/themea.css")] + [InlineData(typeof(TestApplicationA), typeof(TestThemeB), null)] + [InlineData(typeof(TestApplicationB), typeof(TestThemeA), "/server/appb/asserts/css/themea.css")] + [InlineData(typeof(TestApplicationB), typeof(TestThemeB), null)] + [InlineData(typeof(TestApplicationC), typeof(TestThemeA), "/server/asserts/css/themea.css")] + [InlineData(typeof(TestApplicationC), typeof(TestThemeB), null)] + public void ThemeStyle(Type applicationType, Type themeType, string expected) + { + // preconditions + var componentHub = UnitTestFixture.CreateAndRegisterComponentHubMock(); + var application = componentHub.ApplicationManager.GetApplications(applicationType).FirstOrDefault(); + + // test execution + var theme = componentHub.ThemeManager.GetThemes(application, themeType).FirstOrDefault(); + + Assert.NotNull(theme); + Assert.Equal(expected, theme?.ThemeStyle?.ToString()); + } + } +} diff --git a/src/WebExpress.WebCore.Test/Message/UnitTestGetRequest.cs b/src/WebExpress.WebCore.Test/Message/UnitTestGetRequest.cs index dd6252a..a7d6814 100644 --- a/src/WebExpress.WebCore.Test/Message/UnitTestGetRequest.cs +++ b/src/WebExpress.WebCore.Test/Message/UnitTestGetRequest.cs @@ -1,92 +1,77 @@ -using System.IO; -using System.Net.Http; -using Xunit; +using WebExpress.WebCore.Test.Fixture; namespace WebExpress.WebCore.Test.Message { + /// + /// UnitTestGetRequest class for testing HTTP GET requests. + /// + [Collection("NonParallelTests")] public class UnitTestGetRequest { + /// + /// Tests a general GET request. + /// [Fact] - public void Get_General() + public void General() { - var client = new HttpClient(); - //client. + var content = UnitTestFixture.GetEmbeddedResource("general.get"); + var request = UnitTestFixture.CrerateRequestMock(content); - //client.Headers.Add("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.2; .NET CLR 1.0.3705;)"); - - //Stream data = client.OpenRead("http://localhost/"); - //StreamReader reader = new StreamReader(data); - //string s = reader.ReadToEnd(); - //Console.WriteLine(s); - //data.Close(); - //reader.Close(); - - //using var reader = new BinaryReader(new FileStream(Path.Combine("test", "general.get"), FileMode.Open)); - //var request = Request.Create(reader, "127.0.0.1"); - - //Assert.True - //( - // request.Uri?.ToString() == "/abc/xyz/A7BCCCA9-4C7E-4117-9EE2-ECC3381B605A", - // "Fehler in der Funktion Get_General" - //); + Assert.Equal("http://localhost:8080/abc/xyz/A7BCCCA9-4C7E-4117-9EE2-ECC3381B605A", request.Uri?.ToString()); } + /// + /// Tests a GET request with less data. + /// [Fact] - public void Get_Less() + public void Less() { - //using var reader = new BinaryReader(new FileStream(Path.Combine("test", "less.get"), FileMode.Open)); - //var request = Request.Create(reader, "127.0.0.1"); + var content = UnitTestFixture.GetEmbeddedResource("less.get"); + var request = UnitTestFixture.CrerateRequestMock(content); - //Assert.True - //( - // request.Uri?.ToString() == "/abc/xyz/A7BCCCA9-4C7E-4117-9EE2-ECC3381B605A", - // "Fehler in der Funktion Get_Less" - //); + Assert.Equal("http://localhost:8080/abc/xyz/A7BCCCA9-4C7E-4117-9EE2-ECC3381B605A", request.Uri?.ToString()); } + /// + /// Tests a GET request with massive data. + /// [Fact] - public void Get_Massive() + public void Massive() { - //using var reader = new BinaryReader(new FileStream(Path.Combine("test", "massive.get"), FileMode.Open)); - //var request = Request.Create(reader, "127.0.0.1"); + var content = UnitTestFixture.GetEmbeddedResource("massive.get"); + var request = UnitTestFixture.CrerateRequestMock(content); - //Assert.True - //( - // request.Uri?.ToString() == "/abc/xyz/A7BCCCA9-4C7E-4117-9EE2-ECC3381B605A", - // "Fehler in der Funktion Get_Massive" - //); + Assert.Equal("http://localhost:8080/abc/xyz/A7BCCCA9-4C7E-4117-9EE2-ECC3381B605A", request.Uri?.ToString()); } + /// + /// Tests a GET request with parameters. + /// [Fact] - public void Get_Param() + public void GetParameter() { - //using var reader = new BinaryReader(new FileStream(Path.Combine("test", "param.get"), FileMode.Open)); - //var request = Request.Create(reader, "127.0.0.1"); - //var param = request?.GetParameter("a")?.Value; + var content = UnitTestFixture.GetEmbeddedResource("param.get"); + var request = UnitTestFixture.CrerateRequestMock(content); + var param = request?.GetParameter("a")?.Value; - //Assert.True - //( - // request.Uri?.ToString() == "/abc/xyz/A7BCCCA9-4C7E-4117-9EE2-ECC3381B605A" && - // param != null && param == "1", - // "Fehler in der Funktion Get_Param" - //); + Assert.Equal("http://localhost:8080/abc/xyz/A7BCCCA9-4C7E-4117-9EE2-ECC3381B605A", request.Uri?.ToString()); + Assert.Equal("1", param); } + /// + /// Tests a GET request with parameters containing umlauts. + /// [Fact] - public void Get_Param_Umlaut() + public void GetParameterWithUmlaut() { - //using var reader = new BinaryReader(new FileStream(Path.Combine("test", "param_umlaut.get"), FileMode.Open)); - //var request = Request.Create(reader, "127.0.0.1"); - //var a = request?.GetParameter("a")?.Value; - //var b = request?.GetParameter("b")?.Value; + var content = UnitTestFixture.GetEmbeddedResource("param_umlaut.get"); + var request = UnitTestFixture.CrerateRequestMock(content); + var a = request?.GetParameter("a")?.Value; + var b = request?.GetParameter("b")?.Value; - //Assert.True - //( - // request.Uri?.ToString() == "/abc/xyz/A7BCCCA9-4C7E-4117-9EE2-ECC3381B605A" && - // a != null && a == "ä" && - // b != null && b == "ö ü", - // "Fehler in der Funktion Get_Param_Umlaut" - //); + Assert.Equal("http://localhost:8080/abc/xyz/A7BCCCA9-4C7E-4117-9EE2-ECC3381B605A", request.Uri?.ToString()); + Assert.Equal("ä", a); + Assert.Equal("ö ü", b); } } } diff --git a/src/WebExpress.WebCore.Test/Message/UnitTestRequest.cs b/src/WebExpress.WebCore.Test/Message/UnitTestRequest.cs index d11236c..a37e81c 100644 --- a/src/WebExpress.WebCore.Test/Message/UnitTestRequest.cs +++ b/src/WebExpress.WebCore.Test/Message/UnitTestRequest.cs @@ -9,7 +9,7 @@ namespace WebExpress.WebCore.Test.Message public class UnitTestRequest { /// - /// Creates the fFeature collection. + /// Creates the feature collection. /// /// The reader. /// The request method. diff --git a/src/WebExpress.WebCore.Test/Request/Request.cs b/src/WebExpress.WebCore.Test/Request/Request.cs deleted file mode 100644 index c9b81fc..0000000 --- a/src/WebExpress.WebCore.Test/Request/Request.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Xunit; - -namespace WebExpress.WebCore.Test.Request -{ - - public class Request - { - - [Fact] - public void Get_1() - { - Assert.True - ( - true - ); - } - } -} diff --git a/src/WebExpress.WebCore.Test/Route/UnitTestRoute.cs b/src/WebExpress.WebCore.Test/Route/UnitTestRoute.cs new file mode 100644 index 0000000..862f497 --- /dev/null +++ b/src/WebExpress.WebCore.Test/Route/UnitTestRoute.cs @@ -0,0 +1,120 @@ +using WebExpress.WebCore.WebEndpoint; +using WebExpress.WebCore.WebUri; + +namespace WebExpress.WebCore.Test.Route +{ + /// + /// Tests the route. + /// + [Collection("NonParallelTests")] + public class UnitTestRoute + { + /// + /// Test the concat method. + /// + [Theory] + [InlineData("/a/b/c", null, "/a/b/c", 4)] + [InlineData("/a/b/c", "", "/a/b/c", 4)] + [InlineData("/a/b/c", "d", "/a/b/c/d", 5)] + [InlineData("/a/b/c", "/d/e/f", "/a/b/c/d/e/f", 7)] + public void ConcatString(string baseRoute, string segment, string expected, int count) + { + // preconditions + var route = new RouteEndpoint(baseRoute); + + // test execution + var concat = route.Concat(segment); + + Assert.Equal(expected, concat.ToString()); + Assert.Equal(count, concat.PathSegments.Count()); + } + + /// + /// Test the concat method. + /// + [Theory] + [InlineData("/a/b/c", null, "/a/b/c", 4)] + [InlineData("/a/b/c", " ", "/a/b/c", 4)] + [InlineData("/a/b/c", "d", "/a/b/c/d", 5)] + [InlineData("/a/b/c", "/d/e/f", "/a/b/c/d/e/f", 7)] + public void ConcatSegment(string baseRoute, string segment, string expected, int count) + { + // preconditions + var route = new RouteEndpoint(baseRoute); + + // test execution + var concat = route.Concat(segment != null ? [.. segment?.Split('/').Select(x => new UriPathSegmentConstant(x))] : null); + + Assert.Equal(expected, concat.ToString()); + Assert.Equal(count, concat.PathSegments.Count()); + } + + /// + /// Test the combine method. + /// + [Theory] + [InlineData("/a/b/c", null, "/a/b/c")] + [InlineData("/a/b/c", "", "/a/b/c")] + [InlineData("/a/b/c", "d", "/a/b/c/d")] + [InlineData("/a/b/c", "/d/e/f", "/a/b/c/d/e/f")] + public void CombinePath(string baseRoute, string pathB, string expected) + { + // test execution + var combine = RouteEndpoint.Combine([new RouteEndpoint(baseRoute), new RouteEndpoint(pathB)]); + + Assert.Equal(expected, combine.ToString()); + } + + /// + /// Test the combine method. + /// + [Theory] + [InlineData("/a/b/c", null, "/a/b/c")] + [InlineData("/a/b/c", "", "/a/b/c")] + [InlineData("/a/b/c", "d", "/a/b/c/d")] + [InlineData("/a/b/c", "/d/e/f", "/a/b/c/d/e/f")] + public void CombineRoute(string baseRoute, string pathB, string expected) + { + // test execution + var combine = RouteEndpoint.Combine(new RouteEndpoint(baseRoute), [pathB]); + + Assert.Equal(expected, combine.ToString()); + } + + /// + /// Test the combine method. + /// + [Theory] + [InlineData("/a/b/c", null, "/a/b/c")] + [InlineData("/a/b/c", "", "/a/b/c")] + [InlineData("/a/b/c", "d", "/a/b/c/d")] + [InlineData("/a/b/c", "/d/e/f", "/a/b/c/d/e/f")] + public void CombineSegment(string baseRoute, string segment, string expected) + { + // test execution + var combine = RouteEndpoint.Combine(new RouteEndpoint(baseRoute), segment); + + Assert.Equal(expected, combine.ToString()); + } + + /// + /// Test the combine method. + /// + [Theory] + [InlineData("/a/b/c", null, "/a/b/c")] + [InlineData("/a/b/c", "", "/a/b/c")] + [InlineData("/a/b/c", "b", "/a/c")] + [InlineData("/a/b/c", "/b", "/a/c")] + [InlineData("/a/b/c", "/b/c", "/a")] + [InlineData("/a/b/c", "/a/c", "/a/b/c")] + public void RemoveSegment(string route, string segment, string expected) + { + // test execution + var routeEndpoint = new RouteEndpoint(route); + + var removed = routeEndpoint.RemoveSegment(segment); + + Assert.Equal(expected, removed.ToString()); + } + } +} diff --git a/src/WebExpress.WebCore.Test/Schedule/UnitTestClock.cs b/src/WebExpress.WebCore.Test/Schedule/UnitTestClock.cs index 0b04f10..004ba48 100644 --- a/src/WebExpress.WebCore.Test/Schedule/UnitTestClock.cs +++ b/src/WebExpress.WebCore.Test/Schedule/UnitTestClock.cs @@ -1,234 +1,174 @@ -using WebExpress.WebCore.WebJob; +using System.Globalization; +using WebExpress.WebCore.WebJob; namespace WebExpress.WebCore.Test.Schedule { /// /// Tests the scheduler's clock. /// + [Collection("NonParallelTests")] public class UnitTestClock { - [Fact] - public void Synchronize_1() - { + /// + /// Test the synchronization of the clock. + /// + [Theory] + [InlineData(0, 0, 0, 0, 0)] + [InlineData(0, 0, 0, 30, 0)] + [InlineData(0, 0, -5, 0, 5)] + [InlineData(0, 0, 5, 0, 0)] + [InlineData(-1, 0, 0, 0, 24 * 60)] + [InlineData(-1, -10, 0, 0, (24 * 60) + (10 * 60))] + public void Synchronize(int? days, int? hours, int? minutes, int? seconds, int expected) + { + // preconditions var dateTime = DateTime.Now; - var clock = new Clock(DateTime.Now.AddMinutes(-5)); + if (days.HasValue) + { + dateTime = dateTime.AddDays(days.Value); + } - var elapsed = clock.Synchronize(); + if (hours.HasValue) + { + dateTime = dateTime.AddHours(hours.Value); + } - Assert.True - ( - elapsed.Count() == 5 - ); - } + if (minutes.HasValue) + { + dateTime = dateTime.AddMinutes(minutes.Value); + } - [Fact] - public void Synchronize_2() - { - var dateTime = DateTime.Now; + if (seconds.HasValue) + { + dateTime = dateTime.AddSeconds(seconds.Value); + } - var clock = new Clock(DateTime.Now.AddDays(-1)); + var clock = new Clock(dateTime); + // test execution var elapsed = clock.Synchronize(); - Assert.True - ( - elapsed.Count() == 60 * 24 - ); - } - - [Fact] - public void Compare_Equals_1() - { - var clock1 = new Clock(); - var clock2 = new Clock(clock1); - - var res = clock1 == clock2; - - Assert.True - ( - res - ); - } - - [Fact] - public void Compare_Equals_2() - { - var clock1 = new Clock(); - var clock2 = new Clock(DateTime.Now.AddMinutes(5)); - - var res = clock1 == clock2; - - Assert.True - ( - !res - ); - } - - [Fact] - public void Compare_Inequality_1() - { - var clock1 = new Clock(); - var clock2 = new Clock(clock1); - - var res = clock1 != clock2; - - Assert.True - ( - !res - ); - } - - [Fact] - public void Compare_Inequality_2() - { - var clock1 = new Clock(); - var clock2 = new Clock(DateTime.Now.AddMinutes(5)); - - var res = clock1 != clock2; - - Assert.True - ( - res - ); - } - - [Fact] - public void Compare_Less_1() - { - var clock1 = new Clock(); - var clock2 = new Clock(clock1); - - var res = clock1 < clock2; - - Assert.True - ( - !res - ); + Assert.Equal(expected, elapsed.Count()); } - [Fact] - public void Compare_Less_2() - { - var clock1 = new Clock(); - var clock2 = new Clock(DateTime.Now.AddMinutes(5)); - - var res = clock1 < clock2; - - Assert.True - ( - res - ); - } - - [Fact] - public void Compare_Greater_1() - { - var clock1 = new Clock(); - var clock2 = new Clock(clock1); - - var res = clock1 > clock2; - - Assert.True - ( - !res - ); - } - - [Fact] - public void Compare_Greater_2() - { - var clock1 = new Clock(); - var clock2 = new Clock(DateTime.Now.AddMinutes(-5).AddDays(-5)); - - var res = clock1 > clock2; - - Assert.True - ( - res - ); - } - - [Fact] - public void Compare_LessOrEqual_1() - { - var clock1 = new Clock(); - var clock2 = new Clock(clock1); - - var res = clock1 <= clock2; - - Assert.True - ( - res - ); - } - - [Fact] - public void Compare_LessOrEqual_2() - { - var clock1 = new Clock(); - var clock2 = new Clock(DateTime.Now.AddMinutes(5)); - - var res = clock1 <= clock2; - - Assert.True - ( - res - ); - } - - [Fact] - public void Compare_GreaterOrEqual_1() - { - var clock1 = new Clock(); - var clock2 = new Clock(clock1); - - var res = clock1 >= clock2; - - Assert.True - ( - res - ); - } - - [Fact] - public void Compare_GreaterOrEqual_2() - { - var clock1 = new Clock(); - var clock2 = new Clock(DateTime.Now.AddMinutes(-5).AddDays(-5)); - - var res = clock1 >= clock2; - - Assert.True - ( - res - ); - } - - [Fact] - public void Carry_1() - { - var clock1 = new Clock(new DateTime(2020, 12, 31, 23, 59, 0)); - var clock2 = new Clock(new DateTime(2021, 1, 1, 0, 0, 0)); - clock1.Tick(); - - Assert.True - ( - clock1 == clock2 - ); - } - - [Fact] - public void Carry_2() - { - var clock1 = new Clock(new DateTime(2021, 2, 28, 23, 59, 0)); - var clock2 = new Clock(new DateTime(2021, 3, 1, 0, 0, 0)); + /// + /// Test the == operator of the clock. + /// + [Theory] + [InlineData("2020-12-31 23:59:00", "2020-12-31 23:59:00", true)] + [InlineData("2020-12-31 23:59:00", "2021-01-01 00:00:00", false)] + public void CompareEquals(string dateTime1, string dateTime2, bool expected) + { + // preconditions + var clock1 = new Clock(DateTime.ParseExact(dateTime1, "yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture)); + var clock2 = new Clock(DateTime.ParseExact(dateTime2, "yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture)); + + // test execution + Assert.Equal(expected, clock1 == clock2); + } + + /// + /// Test the != operator of the clock. + /// + [Theory] + [InlineData("2020-12-31 23:59:00", "2020-12-31 23:59:00", false)] + [InlineData("2020-12-31 23:59:00", "2021-01-01 00:00:00", true)] + public void CompareInequality(string dateTime1, string dateTime2, bool expected) + { + // preconditions + var clock1 = new Clock(DateTime.ParseExact(dateTime1, "yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture)); + var clock2 = new Clock(DateTime.ParseExact(dateTime2, "yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture)); + + // test execution + Assert.Equal(expected, clock1 != clock2); + } + + /// + /// Test the less operator of the clock. + /// + [Theory] + [InlineData("2020-12-31 23:59:00", "2020-12-31 23:59:00", false)] + [InlineData("2021-01-01 00:00:00", "2020-12-31 23:59:00", false)] + [InlineData("2020-12-31 23:59:00", "2021-01-01 00:00:00", true)] + public void CompareLess(string dateTime1, string dateTime2, bool expected) + { + // preconditions + var clock1 = new Clock(DateTime.ParseExact(dateTime1, "yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture)); + var clock2 = new Clock(DateTime.ParseExact(dateTime2, "yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture)); + + // test execution + Assert.Equal(expected, clock1 < clock2); + } + + /// + /// Test the greater operator of the clock. + /// + [Theory] + [InlineData("2020-12-31 23:59:00", "2020-12-31 23:59:00", false)] + [InlineData("2021-01-01 00:00:00", "2020-12-31 23:59:00", true)] + [InlineData("2020-12-31 23:59:00", "2021-01-01 00:00:00", false)] + public void CompareGreater(string dateTime1, string dateTime2, bool expected) + { + // preconditions + var clock1 = new Clock(DateTime.ParseExact(dateTime1, "yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture)); + var clock2 = new Clock(DateTime.ParseExact(dateTime2, "yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture)); + + // test execution + Assert.Equal(expected, clock1 > clock2); + } + + /// + /// Test the less or equal operator of the clock. + /// + [Theory] + [InlineData("2020-12-31 23:59:00", "2020-12-31 23:59:00", true)] + [InlineData("2021-01-01 00:00:00", "2020-12-31 23:59:00", false)] + [InlineData("2020-12-31 23:59:00", "2021-01-01 00:00:00", true)] + public void CompareLessOrEqual(string dateTime1, string dateTime2, bool expected) + { + // preconditions + var clock1 = new Clock(DateTime.ParseExact(dateTime1, "yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture)); + var clock2 = new Clock(DateTime.ParseExact(dateTime2, "yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture)); + + // test execution + Assert.Equal(expected, clock1 <= clock2); + } + + /// + /// Test the greater or equals operator of the clock. + /// + [Theory] + [InlineData("2020-12-31 23:59:00", "2020-12-31 23:59:00", true)] + [InlineData("2021-01-01 00:00:00", "2020-12-31 23:59:00", true)] + [InlineData("2020-12-31 23:59:00", "2021-01-01 00:00:00", false)] + public void CompareGreaterOrEqual(string dateTime1, string dateTime2, bool expected) + { + // preconditions + var clock1 = new Clock(DateTime.ParseExact(dateTime1, "yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture)); + var clock2 = new Clock(DateTime.ParseExact(dateTime2, "yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture)); + + // test execution + Assert.Equal(expected, clock1 >= clock2); + } + + /// + /// Test the carry of the clock. + /// + [Theory] + [InlineData("2020-12-31 23:59:00", "2021-01-01 00:00:00")] + [InlineData("2021-02-27 23:58:00", "2021-02-27 23:59:00")] + [InlineData("2021-02-28 23:59:00", "2021-03-01 00:00:00")] + [InlineData("2024-02-28 23:59:00", "2024-02-29 00:00:00")] + [InlineData("2024-02-29 23:59:00", "2024-03-01 00:00:00")] + public void Tick(string dateTime1, string expected) + { + var clock1 = new Clock(DateTime.ParseExact(dateTime1, "yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture)); + var clock2 = new Clock(DateTime.ParseExact(expected, "yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture)); clock1.Tick(); - Assert.True - ( - clock1 == clock2 - ); + // test execution + Assert.Equal(clock2, clock1); } } } diff --git a/src/WebExpress.WebCore.Test/Schedule/UnitTestCron.cs b/src/WebExpress.WebCore.Test/Schedule/UnitTestCron.cs index 25b6b0b..65a1400 100644 --- a/src/WebExpress.WebCore.Test/Schedule/UnitTestCron.cs +++ b/src/WebExpress.WebCore.Test/Schedule/UnitTestCron.cs @@ -1,168 +1,133 @@ -using System; -using System.Globalization; -using WebExpress.WebCore.WebComponent; +using WebExpress.WebCore.Test.Fixture; using WebExpress.WebCore.WebJob; -using Xunit; +using WebExpress.WebCore.WebLog; namespace WebExpress.WebCore.Test.Schedule { /// /// Test the cron job of the scheduler. /// + [Collection("NonParallelTests")] public class UnitTestCron { [Fact] public void Create_1() { - var context = new HttpServerContext(null, null, null, null, null, null, null, CultureInfo.CurrentCulture, Log.Current, null); - - ComponentManager.Initialization(context); - + // preconditions + var context = UnitTestFixture.CreateHttpServerContextMock(); var clock = new Clock(); var cron = new Cron(context, "0-59", "*", "1-31", "1-2,3,4,5,6,7,8-10,11,12"); - Assert.True - ( - cron.Matching(clock) - ); + // test execution + Assert.True(cron.Matching(clock)); } [Fact] public void Create_2() { - var context = new HttpServerContext(null, null, null, null, null, null, null, CultureInfo.CurrentCulture, Log.Current, null); + // preconditions + var context = UnitTestFixture.CreateHttpServerContextMock(); var dateTime = DateTime.Now; - - ComponentManager.Initialization(context); - var clock = new Clock(new DateTime(dateTime.Year, 1, dateTime.Day, dateTime.Hour, dateTime.Minute, 0)); var cron = new Cron(context, "*", "*", "0-33", "2, 1-4, x"); - Assert.True - ( - cron.Matching(clock) - ); + // test execution + Assert.True(cron.Matching(clock)); } [Fact] public void Create_3() { - var context = new HttpServerContext(null, null, null, null, null, null, null, CultureInfo.CurrentCulture, Log.Current, null); + // preconditions + var context = UnitTestFixture.CreateHttpServerContextMock(); var dateTime = DateTime.Now; - - ComponentManager.Initialization(context); - var clock = new Clock(new DateTime(dateTime.Year, 12, 31, dateTime.Hour, dateTime.Minute, 0)); var cron = new Cron(context, "*", "*", "31", "12"); - Assert.True - ( - cron.Matching(clock) - ); + // test execution + Assert.True(cron.Matching(clock)); } [Fact] public void Create_4() { - var context = new HttpServerContext(null, null, null, null, null, null, null, CultureInfo.CurrentCulture, Log.Current, null); + // preconditions + var context = UnitTestFixture.CreateHttpServerContextMock(); var dateTime = DateTime.Now; Log.Current.Clear(); - ComponentManager.Initialization(context); - var clock = new Clock(new DateTime(dateTime.Year, 12, 31, dateTime.Hour, dateTime.Minute, 0)); var cron = new Cron(context, "*", "*", "*", "a"); - Assert.True - ( - context.Log.WarningCount == 1 - ); + // test execution + Assert.Equal(1, context.Log.WarningCount); } [Fact] public void Create_5() { - var context = new HttpServerContext(null, null, null, null, null, null, null, CultureInfo.CurrentCulture, Log.Current, null); + // preconditions + var context = UnitTestFixture.CreateHttpServerContextMock(); var dateTime = DateTime.Now; - - ComponentManager.Initialization(context); - var clock = new Clock(new DateTime(dateTime.Year, 12, 31, dateTime.Hour, dateTime.Minute, 0)); var cron = new Cron(context, "*", "*", "*", ""); - Assert.True - ( - cron.Matching(clock) - ); + // test execution + Assert.True(cron.Matching(clock)); } [Fact] public void Create_6() { - var context = new HttpServerContext(null, null, null, null, null, null, null, CultureInfo.CurrentCulture, Log.Current, null); + // preconditions + var context = UnitTestFixture.CreateHttpServerContextMock(); var dateTime = DateTime.Now; Log.Current.Clear(); - ComponentManager.Initialization(context); - var clock = new Clock(new DateTime(dateTime.Year, 12, 31, dateTime.Hour, dateTime.Minute, 0)); var cron = new Cron(context, "99", "*", "*", "*"); - Assert.True - ( - context.Log.WarningCount == 1 - ); + // test execution + Assert.Equal(1, context.Log.WarningCount); } [Fact] public void Matching_1() { - var context = new HttpServerContext(null, null, null, null, null, null, null, CultureInfo.CurrentCulture, Log.Current, null); + // preconditions + var context = UnitTestFixture.CreateHttpServerContextMock(); var dateTime = DateTime.Now; - - ComponentManager.Initialization(context); - var clock = new Clock(new DateTime(dateTime.Year, 12, 31, dateTime.Hour, dateTime.Minute, 0)); var cron = new Cron(context, "*", "*", "31", "1-11"); - Assert.True - ( - !cron.Matching(clock) - ); + // test execution + Assert.False(cron.Matching(clock)); } [Fact] public void Matching_2() { - var context = new HttpServerContext(null, null, null, null, null, null, null, CultureInfo.CurrentCulture, Log.Current, null); + // preconditions + var context = UnitTestFixture.CreateHttpServerContextMock(); var dateTime = DateTime.Now; - - ComponentManager.Initialization(context); - var clock = new Clock(new DateTime(2020, 1, 1, dateTime.Hour, dateTime.Minute, 0)); // wednesday var cron = new Cron(context, "*", "*", "*", "*", "3"); // wednesday - Assert.True - ( - cron.Matching(clock) - ); + // test execution + Assert.True(cron.Matching(clock)); } [Fact] public void Matching_3() { - var context = new HttpServerContext(null, null, null, null, null, null, null, CultureInfo.CurrentCulture, Log.Current, null); + // preconditions + var context = UnitTestFixture.CreateHttpServerContextMock(); var dateTime = DateTime.Now; - - ComponentManager.Initialization(context); - var clock = new Clock(new DateTime(2020, 1, 1, dateTime.Hour, dateTime.Minute, 0)); // wednesday var cron = new Cron(context, "*", "*", "*", "*", "1"); // sunday - Assert.True - ( - !cron.Matching(clock) - ); + // test execution + Assert.False(cron.Matching(clock)); } } } diff --git a/src/WebExpress.WebCore.Test/TestApplicationA.cs b/src/WebExpress.WebCore.Test/TestApplicationA.cs new file mode 100644 index 0000000..5b687a7 --- /dev/null +++ b/src/WebExpress.WebCore.Test/TestApplicationA.cs @@ -0,0 +1,45 @@ +using WebExpress.WebCore.WebApplication; +using WebExpress.WebCore.WebAttribute; + +namespace WebExpress.WebCore.Test +{ + /// + /// A dummy application for testing purposes. + /// + [Name("TestApplicationA")] + [Description("application.description")] + [Icon("/assets/img/Logo.png")] + [ContextPath("/appa")] + [AssetPath("/asseta")] + [DataPath("/dataa")] + [Dependency("webexpress.webui")] + public sealed class TestApplicationA : IApplication + { + /// + /// Initializes a new instance of the class. + /// + /// The application context, for testing the injection. + private TestApplicationA(IApplicationContext applicationContext) + { + // test the injection + if (applicationContext == null) + { + throw new ArgumentNullException(nameof(applicationContext), "Parameter cannot be null or empty."); + } + } + + /// + /// Called when the plugin starts working. The call is concurrent. + /// + public void Run() + { + } + + /// + /// Release of unmanaged resources reserved during use. + /// + public void Dispose() + { + } + } +} diff --git a/src/WebExpress.WebCore.Test/TestApplicationB.cs b/src/WebExpress.WebCore.Test/TestApplicationB.cs new file mode 100644 index 0000000..5af1ea7 --- /dev/null +++ b/src/WebExpress.WebCore.Test/TestApplicationB.cs @@ -0,0 +1,39 @@ +using WebExpress.WebCore.WebApplication; +using WebExpress.WebCore.WebAttribute; + +namespace WebExpress.WebCore.Test +{ + /// + /// A dummy application for testing purposes. + /// + [Name("TestApplicationB")] + [Description("application.description")] + [Icon("/assets/img/Logo.png")] + [ContextPath("/appb")] + [AssetPath("/assetb")] + [DataPath("/datab")] + [Dependency("webexpress.webui")] + public sealed class TestApplicationB : IApplication + { + /// + /// Initializes a new instance of the class. + /// + private TestApplicationB() + { + } + + /// + /// Called when the plugin starts working. The call is concurrent. + /// + public void Run() + { + } + + /// + /// Release of unmanaged resources reserved during use. + /// + public void Dispose() + { + } + } +} diff --git a/src/WebExpress.WebCore.Test/TestApplicationC.cs b/src/WebExpress.WebCore.Test/TestApplicationC.cs new file mode 100644 index 0000000..5adfc91 --- /dev/null +++ b/src/WebExpress.WebCore.Test/TestApplicationC.cs @@ -0,0 +1,51 @@ +using WebExpress.WebCore.WebApplication; +using WebExpress.WebCore.WebAttribute; +using WebExpress.WebCore.WebPlugin; + +namespace WebExpress.WebCore.Test +{ + /// + /// A dummy application for testing purposes. + /// + [Id("TestApplicationC")] + [Name("TestApplicationC")] + [Description("application.description")] + [Icon("/assets/img/Logo.png")] + [Dependency("webexpress.webui")] + public sealed class TestApplicationC : IApplication + { + /// + /// Initializes a new instance of the class. + /// + /// The application context, for testing the injection. + /// The plugin manager, for testing the injection. + private TestApplicationC(IApplicationContext applicationContext, IPluginManager pluginManager) + { + // test the injection + if (applicationContext == null) + { + throw new ArgumentNullException(nameof(applicationContext), "Parameter cannot be null or empty."); + } + + // test the injection + if (pluginManager == null) + { + throw new ArgumentNullException(nameof(pluginManager), "Parameter cannot be null or empty."); + } + } + + /// + /// Called when the plugin starts working. The call is concurrent. + /// + public void Run() + { + } + + /// + /// Release of unmanaged resources reserved during use. + /// + public void Dispose() + { + } + } +} diff --git a/src/WebExpress.WebCore.Test/TestConditionAlwaysFalse.cs b/src/WebExpress.WebCore.Test/TestConditionAlwaysFalse.cs new file mode 100644 index 0000000..c77a1c1 --- /dev/null +++ b/src/WebExpress.WebCore.Test/TestConditionAlwaysFalse.cs @@ -0,0 +1,21 @@ +using WebExpress.WebCore.WebCondition; +using WebExpress.WebCore.WebMessage; + +namespace WebExpress.WebCore.Test +{ + /// + /// Represents a test condition that never becomes true. + /// + public class TestConditionAlwaysFalse : ICondition + { + /// + /// Check whether the condition is fulfilled. + /// + /// The request. + /// True if the condition is fulfilled, false otherwise. + public bool Fulfillment(Request request) + { + return false; + } + } +} diff --git a/src/WebExpress.WebCore.Test/TestEventA.cs b/src/WebExpress.WebCore.Test/TestEventA.cs new file mode 100644 index 0000000..788746f --- /dev/null +++ b/src/WebExpress.WebCore.Test/TestEventA.cs @@ -0,0 +1,11 @@ +using WebExpress.WebCore.WebEvent; + +namespace WebExpress.WebCore.Test +{ + /// + /// Represents a test event. + /// + internal class TestEventA : IEvent + { + } +} diff --git a/src/WebExpress.WebCore.Test/TestEventArgument.cs b/src/WebExpress.WebCore.Test/TestEventArgument.cs new file mode 100644 index 0000000..eb5c2f4 --- /dev/null +++ b/src/WebExpress.WebCore.Test/TestEventArgument.cs @@ -0,0 +1,15 @@ +using WebExpress.WebCore.WebEvent; + +namespace WebExpress.WebCore.Test +{ + /// + /// Represents a test event argument. + /// + public class TestEventArgument : IEventArgument + { + /// + /// Returns or sets a boolean value for testing purposes. + /// + public bool TestProperty { get; set; } + } +} \ No newline at end of file diff --git a/src/WebExpress.WebCore.Test/TestEventB.cs b/src/WebExpress.WebCore.Test/TestEventB.cs new file mode 100644 index 0000000..2b20742 --- /dev/null +++ b/src/WebExpress.WebCore.Test/TestEventB.cs @@ -0,0 +1,11 @@ +using WebExpress.WebCore.WebEvent; + +namespace WebExpress.WebCore.Test +{ + /// + /// Represents a test event. + /// + internal class TestEventB : IEvent + { + } +} diff --git a/src/WebExpress.WebCore.Test/TestEventHandlerA.cs b/src/WebExpress.WebCore.Test/TestEventHandlerA.cs new file mode 100644 index 0000000..c93ae04 --- /dev/null +++ b/src/WebExpress.WebCore.Test/TestEventHandlerA.cs @@ -0,0 +1,67 @@ +using WebExpress.WebCore.WebApplication; +using WebExpress.WebCore.WebAttribute; +using WebExpress.WebCore.WebEvent; + +namespace WebExpress.WebCore.Test +{ + /// + /// A dummy event handler for testing purposes. + /// + [Application()] + [Event] + public sealed class TestEventHandlerA : IEventHandler + { + /// + /// Initialization of the event. + /// + /// The event handler context, for testing the injection. + /// The application manager, for testing the injection. + private TestEventHandlerA(IEventHandlerContext eventHandlerContext, IApplicationManager applicationManager) + { + // test the injection + if (eventHandlerContext == null) + { + throw new ArgumentNullException(nameof(eventHandlerContext), "Parameter cannot be null or empty."); + } + + // test the injection + if (applicationManager == null) + { + throw new ArgumentNullException(nameof(applicationManager), "Parameter cannot be null or empty."); + } + } + + /// + /// Process the event. + /// + /// The object that triggered the event. + /// The argument for the event. + public void Process(object sender, IEventArgument eventArgument) + { + // test the parameter + if (sender == null) + { + throw new ArgumentNullException(nameof(sender), "Parameter cannot be null or empty."); + } + + // test the parameter + if (eventArgument == null) + { + throw new ArgumentNullException(nameof(eventArgument), "Parameter cannot be null or empty."); + } + + // to check in the test whether the handler has been executed correctly + if (eventArgument is TestEventArgument arg) + { + arg.TestProperty = true; + } + } + + /// + /// Release of unmanaged resources reserved during use. + /// + public void Dispose() + { + } + } +} diff --git a/src/WebExpress.WebCore.Test/TestEventHandlerB.cs b/src/WebExpress.WebCore.Test/TestEventHandlerB.cs new file mode 100644 index 0000000..542d528 --- /dev/null +++ b/src/WebExpress.WebCore.Test/TestEventHandlerB.cs @@ -0,0 +1,64 @@ +using WebExpress.WebCore.WebApplication; +using WebExpress.WebCore.WebAttribute; +using WebExpress.WebCore.WebEvent; + +namespace WebExpress.WebCore.Test +{ + /// + /// A dummy event handler for testing purposes. + /// + [Application()] + [Event] + public sealed class TestEventHandlerB : IEventHandler + { + /// + /// Initialization of the event. + /// + /// The event handler context, for testing the injection. + /// The application manager, for testing the injection. + private TestEventHandlerB(IEventHandlerContext eventHandlerContext, IApplicationManager applicationManager) + { + // test the injection + if (eventHandlerContext == null) + { + throw new ArgumentNullException(nameof(eventHandlerContext), "Parameter cannot be null or empty."); + } + + // test the injection + if (applicationManager == null) + { + throw new ArgumentNullException(nameof(applicationManager), "Parameter cannot be null or empty."); + } + } + + /// + /// Process the event. + /// + /// The object that triggered the event. + /// The argument for the event. + public void Process(object sender, TestEventArgument eventArgument) + { + // test the parameter + if (sender == null) + { + throw new ArgumentNullException(nameof(sender), "Parameter cannot be null or empty."); + } + + // test the parameter + if (eventArgument == null) + { + throw new ArgumentNullException(nameof(eventArgument), "Parameter cannot be null or empty."); + } + + // to check in the test whether the handler has been executed correctly + eventArgument.TestProperty = true; + } + + /// + /// Release of unmanaged resources reserved during use. + /// + public void Dispose() + { + } + } +} diff --git a/src/WebExpress.WebCore.Test/TestFragmentA.cs b/src/WebExpress.WebCore.Test/TestFragmentA.cs new file mode 100644 index 0000000..cfba253 --- /dev/null +++ b/src/WebExpress.WebCore.Test/TestFragmentA.cs @@ -0,0 +1,65 @@ +using WebExpress.WebCore.Test.WWW; +using WebExpress.WebCore.WebAttribute; +using WebExpress.WebCore.WebComponent; +using WebExpress.WebCore.WebFragment; +using WebExpress.WebCore.WebHtml; +using WebExpress.WebCore.WebPage; + +namespace WebExpress.WebCore.Test +{ + /// + /// Represents a test fragment. + /// + [Section()] + [Scope] + [Scope] + [Order(0)] + public sealed class TestFragmentA : IFragment + { + /// + /// Initialization of the fragment. Here, for example, managed resources can be loaded. + /// + /// The component hub. + /// The context of the fragment. + /// The unique identifier for the fragment. + public TestFragmentA(IComponentHub componentHub, IFragmentContext fragmentContext, IComponentId id) + { + // test the injection + if (componentHub == null) + { + throw new ArgumentNullException(nameof(componentHub), "Parameter componentHub cannot be null or empty."); + } + + // test the injection + if (fragmentContext == null) + { + throw new ArgumentNullException(nameof(fragmentContext), "Parameter fragmentContext cannot be null or empty."); + } + + // test the injection + if (string.IsNullOrWhiteSpace(id?.ToString())) + { + throw new ArgumentNullException(nameof(fragmentContext), "Parameter id cannot be null or empty."); + } + } + + /// + /// Processes the fragments in the specified render context. + /// + /// The context in which rendering occurs. + /// The visual tree used for rendering the fragment. + /// An HTML node representing the rendered fragments. + public IHtmlNode Render(IRenderContext renderContext, IVisualTree visualTree) + { + return new HtmlText("TestFragmentA"); + } + + /// + /// Disposes the resources used by the fragment. + /// + public void Dispose() + { + + } + } +} diff --git a/src/WebExpress.WebCore.Test/TestFragmentB.cs b/src/WebExpress.WebCore.Test/TestFragmentB.cs new file mode 100644 index 0000000..99e12f6 --- /dev/null +++ b/src/WebExpress.WebCore.Test/TestFragmentB.cs @@ -0,0 +1,56 @@ +using WebExpress.WebCore.WebAttribute; +using WebExpress.WebCore.WebComponent; +using WebExpress.WebCore.WebFragment; +using WebExpress.WebCore.WebHtml; +using WebExpress.WebCore.WebPage; + +namespace WebExpress.WebCore.Test +{ + /// + /// Represents a test fragment. + /// + [Section()] + [Scope] + [Order(0)] + public sealed class TestFragmentB : IFragment + { + /// + /// Initialization of the fragment. Here, for example, managed resources can be loaded. + /// + /// The component hub. + /// The context of the fragment. + public TestFragmentB(IComponentHub componentHub, IFragmentContext fragmentContext) + { + // test the injection + if (componentHub == null) + { + throw new ArgumentNullException(nameof(componentHub), "Parameter cannot be null or empty."); + } + + // test the injection + if (fragmentContext == null) + { + throw new ArgumentNullException(nameof(fragmentContext), "Parameter cannot be null or empty."); + } + } + + /// + /// Processes the fragments in the specified render context. + /// + /// The context in which rendering occurs. + /// The visual tree used for rendering the fragment. + /// An HTML node representing the rendered fragments. + public IHtmlNode Render(RenderContext renderContext, VisualTree visualTree) + { + return new HtmlText("TestFragmentB"); + } + + /// + /// Disposes the resources used by the fragment. + /// + public void Dispose() + { + + } + } +} diff --git a/src/WebExpress.WebCore.Test/TestFragmentC.cs b/src/WebExpress.WebCore.Test/TestFragmentC.cs new file mode 100644 index 0000000..4470b51 --- /dev/null +++ b/src/WebExpress.WebCore.Test/TestFragmentC.cs @@ -0,0 +1,55 @@ +using WebExpress.WebCore.WebAttribute; +using WebExpress.WebCore.WebComponent; +using WebExpress.WebCore.WebFragment; +using WebExpress.WebCore.WebHtml; + +namespace WebExpress.WebCore.Test +{ + /// + /// Represents a test fragment. + /// + [Section()] + [Scope] + [Order(0)] + public sealed class TestFragmentC : IFragment + { + /// + /// Initialization of the fragment. Here, for example, managed resources can be loaded. + /// + /// The component hub. + /// The context of the fragment. + public TestFragmentC(IComponentHub componentHub, IFragmentContext fragmentContext) + { + // test the injection + if (componentHub == null) + { + throw new ArgumentNullException(nameof(componentHub), "Parameter cannot be null or empty."); + } + + // test the injection + if (fragmentContext == null) + { + throw new ArgumentNullException(nameof(fragmentContext), "Parameter cannot be null or empty."); + } + } + + /// + /// Processes the fragments in the specified render context. + /// + /// The context in which rendering occurs. + /// The visual tree used for rendering the fragment. + /// An HTML node representing the rendered fragments. + public IHtmlNode Render(TestRenderContext renderContext, TestVisualTree visualTree) + { + return new HtmlText("TestFragmentC"); + } + + /// + /// Disposes the resources used by the fragment. + /// + public void Dispose() + { + + } + } +} diff --git a/src/WebExpress.WebCore.Test/TestFragmentD.cs b/src/WebExpress.WebCore.Test/TestFragmentD.cs new file mode 100644 index 0000000..dc7f64d --- /dev/null +++ b/src/WebExpress.WebCore.Test/TestFragmentD.cs @@ -0,0 +1,56 @@ +using WebExpress.WebCore.WebAttribute; +using WebExpress.WebCore.WebComponent; +using WebExpress.WebCore.WebFragment; +using WebExpress.WebCore.WebHtml; + +namespace WebExpress.WebCore.Test +{ + /// + /// Represents a test fragment that should never be displayed due to the condition. + /// + [Section()] + [Scope] + [Order(0)] + [Condition] + public sealed class TestFragmentD : IFragment + { + /// + /// Initialization of the fragment. Here, for example, managed resources can be loaded. + /// + /// The component hub. + /// The context of the fragment. + public TestFragmentD(IComponentHub componentHub, IFragmentContext fragmentContext) + { + // test the injection + if (componentHub == null) + { + throw new ArgumentNullException(nameof(componentHub), "Parameter cannot be null or empty."); + } + + // test the injection + if (fragmentContext == null) + { + throw new ArgumentNullException(nameof(fragmentContext), "Parameter cannot be null or empty."); + } + } + + /// + /// Processes the fragments in the specified render context. + /// + /// The context in which rendering occurs. + /// The visual tree used for rendering the fragment. + /// An HTML node representing the rendered fragments. + public IHtmlNode Render(TestRenderContext renderContext, TestVisualTree visualTree) + { + return new HtmlText("TestFragmentD"); + } + + /// + /// Disposes the resources used by the fragment. + /// + public void Dispose() + { + + } + } +} diff --git a/src/WebExpress.WebCore.Test/TestIconBell.cs b/src/WebExpress.WebCore.Test/TestIconBell.cs new file mode 100644 index 0000000..2152f3e --- /dev/null +++ b/src/WebExpress.WebCore.Test/TestIconBell.cs @@ -0,0 +1,28 @@ +using WebExpress.WebCore.WebHtml; +using WebExpress.WebCore.WebIcon; +using WebExpress.WebCore.WebPage; + +namespace WebExpress.WebCore.Test +{ + /// + /// Represents a test icon that can be rendered to HTML. + /// + public class TestIconBell : IIcon + { + /// + /// Converts the icon to an HTML representation. + /// + /// The context in which the icon is rendered. + /// The visual tree representing the icon's structure. + /// The id attribute of the HTML element. + /// The description of the icon. + /// The CSS class of the HTML element. + /// The inline style of the HTML element. + /// The ARIA role of the HTML element. + /// An HTML node representing the rendered icon. + public IHtmlNode Render(IRenderContext renderContext, IVisualTree visualTree, string id = null, string description = null, string css = null, string style = null, string role = null) + { + return new HtmlElementTextSemanticsSpan(new HtmlText("🔔")); + } + } +} diff --git a/src/WebExpress.WebCore.Test/TestIconPalette.cs b/src/WebExpress.WebCore.Test/TestIconPalette.cs new file mode 100644 index 0000000..1dee1c9 --- /dev/null +++ b/src/WebExpress.WebCore.Test/TestIconPalette.cs @@ -0,0 +1,28 @@ +using WebExpress.WebCore.WebHtml; +using WebExpress.WebCore.WebIcon; +using WebExpress.WebCore.WebPage; + +namespace WebExpress.WebCore.Test +{ + /// + /// Represents a test icon that can be rendered to HTML. + /// + public class TestIconPalette : IIcon + { + /// + /// Converts the icon to an HTML representation. + /// + /// The context in which the icon is rendered. + /// The visual tree representing the icon's structure. + /// The id attribute of the HTML element. + /// The description of the icon. + /// The CSS class of the HTML element. + /// The inline style of the HTML element. + /// The ARIA role of the HTML element. + /// An HTML node representing the rendered icon. + public IHtmlNode Render(IRenderContext renderContext, IVisualTree visualTree, string id = null, string description = null, string css = null, string style = null, string role = null) + { + return new HtmlElementTextSemanticsSpan(new HtmlText("🎨")); + } + } +} diff --git a/src/WebExpress.WebCore.Test/TestIconProfile.cs b/src/WebExpress.WebCore.Test/TestIconProfile.cs new file mode 100644 index 0000000..2ca070e --- /dev/null +++ b/src/WebExpress.WebCore.Test/TestIconProfile.cs @@ -0,0 +1,28 @@ +using WebExpress.WebCore.WebHtml; +using WebExpress.WebCore.WebIcon; +using WebExpress.WebCore.WebPage; + +namespace WebExpress.WebCore.Test +{ + /// + /// Represents a test icon that can be rendered to HTML. + /// + public class TestIconProfile : IIcon + { + /// + /// Converts the icon to an HTML representation. + /// + /// The context in which the icon is rendered. + /// The visual tree representing the icon's structure. + /// The id attribute of the HTML element. + /// The description of the icon. + /// The CSS class of the HTML element. + /// The inline style of the HTML element. + /// The ARIA role of the HTML element. + /// An HTML node representing the rendered icon. + public IHtmlNode Render(IRenderContext renderContext, IVisualTree visualTree, string id = null, string description = null, string css = null, string style = null, string role = null) + { + return new HtmlElementTextSemanticsSpan(new HtmlText("👤")); + } + } +} diff --git a/src/WebExpress.WebCore.Test/TestIconShild.cs b/src/WebExpress.WebCore.Test/TestIconShild.cs new file mode 100644 index 0000000..c8c786e --- /dev/null +++ b/src/WebExpress.WebCore.Test/TestIconShild.cs @@ -0,0 +1,29 @@ + +using WebExpress.WebCore.WebHtml; +using WebExpress.WebCore.WebIcon; +using WebExpress.WebCore.WebPage; + +namespace WebExpress.WebCore.Test +{ + /// + /// Represents a test icon that can be rendered to HTML. + /// + public class TestIconShild : IIcon + { + /// + /// Converts the icon to an HTML representation. + /// + /// The context in which the icon is rendered. + /// The visual tree representing the icon's structure. + /// The id attribute of the HTML element. + /// The description of the icon. + /// The CSS class of the HTML element. + /// The inline style of the HTML element. + /// The ARIA role of the HTML element. + /// An HTML node representing the rendered icon. + public IHtmlNode Render(IRenderContext renderContext, IVisualTree visualTree, string id = null, string description = null, string css = null, string style = null, string role = null) + { + return new HtmlElementTextSemanticsSpan(new HtmlText("🛡")); + } + } +} diff --git a/src/WebExpress.WebCore.Test/TestIconTool.cs b/src/WebExpress.WebCore.Test/TestIconTool.cs new file mode 100644 index 0000000..86b6425 --- /dev/null +++ b/src/WebExpress.WebCore.Test/TestIconTool.cs @@ -0,0 +1,29 @@ + +using WebExpress.WebCore.WebHtml; +using WebExpress.WebCore.WebIcon; +using WebExpress.WebCore.WebPage; + +namespace WebExpress.WebCore.Test +{ + /// + /// Represents a test icon that can be rendered to HTML. + /// + public class TestIconTool : IIcon + { + /// + /// Converts the icon to an HTML representation. + /// + /// The context in which the icon is rendered. + /// The visual tree representing the icon's structure. + /// The id attribute of the HTML element. + /// The description of the icon. + /// The CSS class of the HTML element. + /// The inline style of the HTML element. + /// The ARIA role of the HTML element. + /// An HTML node representing the rendered icon. + public IHtmlNode Render(IRenderContext renderContext, IVisualTree visualTree, string id = null, string description = null, string css = null, string style = null, string role = null) + { + return new HtmlElementTextSemanticsSpan(new HtmlText("🛠")); + } + } +} diff --git a/src/WebExpress.WebCore.Test/TestIconWrench.cs b/src/WebExpress.WebCore.Test/TestIconWrench.cs new file mode 100644 index 0000000..487b1be --- /dev/null +++ b/src/WebExpress.WebCore.Test/TestIconWrench.cs @@ -0,0 +1,29 @@ + +using WebExpress.WebCore.WebHtml; +using WebExpress.WebCore.WebIcon; +using WebExpress.WebCore.WebPage; + +namespace WebExpress.WebCore.Test +{ + /// + /// Represents a test icon that can be rendered to HTML. + /// + public class TestIconWrench : IIcon + { + /// + /// Converts the icon to an HTML representation. + /// + /// The context in which the icon is rendered. + /// The visual tree representing the icon's structure. + /// The id attribute of the HTML element. + /// The description of the icon. + /// The CSS class of the HTML element. + /// The inline style of the HTML element. + /// The ARIA role of the HTML element. + /// An HTML node representing the rendered icon. + public IHtmlNode Render(IRenderContext renderContext, IVisualTree visualTree, string id = null, string description = null, string css = null, string style = null, string role = null) + { + return new HtmlElementTextSemanticsSpan(new HtmlText("🔧")); + } + } +} diff --git a/src/WebExpress.WebCore.Test/TestIdentityPermissionA.cs b/src/WebExpress.WebCore.Test/TestIdentityPermissionA.cs new file mode 100644 index 0000000..a35920b --- /dev/null +++ b/src/WebExpress.WebCore.Test/TestIdentityPermissionA.cs @@ -0,0 +1,21 @@ +using WebExpress.WebCore.WebAttribute; +using WebExpress.WebCore.WebIdentity; + +namespace WebExpress.WebCore.Test +{ + /// + /// A dummy permission. + /// + [Name("Read")] + [Description("Permissions to read.")] + [Role()] + public sealed class TestIdentityPermissionA : IIdentityPermission + { + /// + /// Releases all resources used by the current instance of the class. + /// + public void Dispose() + { + } + } +} diff --git a/src/WebExpress.WebCore.Test/TestIdentityPermissionB.cs b/src/WebExpress.WebCore.Test/TestIdentityPermissionB.cs new file mode 100644 index 0000000..d45da1c --- /dev/null +++ b/src/WebExpress.WebCore.Test/TestIdentityPermissionB.cs @@ -0,0 +1,21 @@ +using WebExpress.WebCore.WebAttribute; +using WebExpress.WebCore.WebIdentity; + +namespace WebExpress.WebCore.Test +{ + /// + /// A dummy permission. + /// + [Name("Write")] + [Description("Permissions to write.")] + [Role()] + public sealed class TestIdentityPermissionB : IIdentityPermission + { + /// + /// Releases all resources used by the current instance of the class. + /// + public void Dispose() + { + } + } +} diff --git a/src/WebExpress.WebCore.Test/TestIdentityPermissionC.cs b/src/WebExpress.WebCore.Test/TestIdentityPermissionC.cs new file mode 100644 index 0000000..d20e621 --- /dev/null +++ b/src/WebExpress.WebCore.Test/TestIdentityPermissionC.cs @@ -0,0 +1,21 @@ +using WebExpress.WebCore.WebAttribute; +using WebExpress.WebCore.WebIdentity; + +namespace WebExpress.WebCore.Test +{ + /// + /// A dummy permission. + /// + [Name("Delte")] + [Description("Permissions to delete.")] + [Role()] + public sealed class TestIdentityPermissionC : IIdentityPermission + { + /// + /// Releases all resources used by the current instance of the class. + /// + public void Dispose() + { + } + } +} diff --git a/src/WebExpress.WebCore.Test/TestIdentityRoleA.cs b/src/WebExpress.WebCore.Test/TestIdentityRoleA.cs new file mode 100644 index 0000000..c10ddf6 --- /dev/null +++ b/src/WebExpress.WebCore.Test/TestIdentityRoleA.cs @@ -0,0 +1,20 @@ +using WebExpress.WebCore.WebAttribute; +using WebExpress.WebCore.WebIdentity; + +namespace WebExpress.WebCore.Test +{ + /// + /// A dummy role. + /// + [Name("Admin")] + [Description("Has permissions to create, edit, and delete data.")] + public sealed class TestIdentityRoleA : IIdentityRole + { + /// + /// Releases all resources used by the current instance of the class. + /// + public void Dispose() + { + } + } +} diff --git a/src/WebExpress.WebCore.Test/TestIdentityRoleB.cs b/src/WebExpress.WebCore.Test/TestIdentityRoleB.cs new file mode 100644 index 0000000..0f6abf4 --- /dev/null +++ b/src/WebExpress.WebCore.Test/TestIdentityRoleB.cs @@ -0,0 +1,22 @@ +using WebExpress.WebCore.WebAttribute; +using WebExpress.WebCore.WebIdentity; + +namespace WebExpress.WebCore.Test +{ + /// + /// A dummy role. + /// + [Name("Editor")] + [Description("Has permissions to create and edit, but not delete.")] + [Permission()] + [Permission()] + public sealed class TestIdentityRoleB : IIdentityRole + { + /// + /// Releases all resources used by the current instance of the class. + /// + public void Dispose() + { + } + } +} diff --git a/src/WebExpress.WebCore.Test/TestJobA.cs b/src/WebExpress.WebCore.Test/TestJobA.cs new file mode 100644 index 0000000..8794b38 --- /dev/null +++ b/src/WebExpress.WebCore.Test/TestJobA.cs @@ -0,0 +1,39 @@ +using WebExpress.WebCore.WebAttribute; +using WebExpress.WebCore.WebJob; + +namespace WebExpress.WebCore.Test +{ + /// + /// A dummy job for testing purposes. + /// + [Job("50", "8", "31", "1-2", "Saturday")] + public sealed class TestJobA : IJob + { + /// + /// Initialization of the job. + /// + /// The job context, for testing the injection. + private TestJobA(IJobContext jobContext) + { + // test the injection + if (jobContext == null) + { + throw new ArgumentNullException(nameof(jobContext), "Parameter cannot be null or empty."); + } + } + + /// + /// Called when the jobs starts working. The call is concurrent. + /// + public void Process() + { + } + + /// + /// Release of unmanaged resources reserved during use. + /// + public void Dispose() + { + } + } +} diff --git a/src/WebExpress.WebCore.Test/TestParameterA.cs b/src/WebExpress.WebCore.Test/TestParameterA.cs new file mode 100644 index 0000000..e77468b --- /dev/null +++ b/src/WebExpress.WebCore.Test/TestParameterA.cs @@ -0,0 +1,39 @@ +using WebExpress.WebCore.WebMessage; + +namespace WebExpress.WebCore.Test +{ + /// + /// Represents a test parameter. + /// + internal class TestParameterA : Parameter + { + /// + /// Initializes a new instance of the class. + /// + public TestParameterA() + : base("TestParameterA", null, ParameterScope.Url) + { + + } + + /// + /// Initializes a new instance of the class with a specified value. + /// + /// The value of the parameter. + public TestParameterA(int value) + : base("TestParameterA", value, ParameterScope.Url) + { + + } + + /// + /// Initializes a new instance of the class with a specified value. + /// + /// The value of the parameter. + public TestParameterA(Guid value) + : base("TestParameterA", value.ToString(), ParameterScope.Url) + { + + } + } +} diff --git a/src/WebExpress.WebCore.Test/TestPlugin.cs b/src/WebExpress.WebCore.Test/TestPlugin.cs new file mode 100644 index 0000000..637db78 --- /dev/null +++ b/src/WebExpress.WebCore.Test/TestPlugin.cs @@ -0,0 +1,41 @@ +using WebExpress.WebCore.WebApplication; +using WebExpress.WebCore.WebAttribute; +using WebExpress.WebCore.WebPlugin; + +namespace WebExpress.WebCore.Test +{ + /// + /// A dummy plugin for testing purposes. + /// + [Name("TestPlugin")] + [Description("plugin.description")] + [Icon("/assets/img/Logo.png")] + [Application()] + [Application()] + [Application()] + public sealed class TestPlugin : IPlugin + { + /// + /// Initializes a new instance of the class. + /// + /// The plugin context. + private TestPlugin(IPluginContext pluginContext) + { + + } + + /// + /// Called when the plugin starts working. The call is concurrent. + /// + public void Run() + { + } + + /// + /// Release of unmanaged resources reserved during use. + /// + public void Dispose() + { + } + } +} diff --git a/src/WebExpress.WebCore.Test/TestRenderContext.cs b/src/WebExpress.WebCore.Test/TestRenderContext.cs new file mode 100644 index 0000000..5b84420 --- /dev/null +++ b/src/WebExpress.WebCore.Test/TestRenderContext.cs @@ -0,0 +1,40 @@ +using WebExpress.WebCore.WebEndpoint; +using WebExpress.WebCore.WebMessage; +using WebExpress.WebCore.WebPage; + +namespace WebExpress.WebCore.Test +{ + /// + /// A custom render context for testing purposes. + /// + public class TestRenderContext : IRenderContext + { + /// + /// Returns the endpoint associated with the rendering context. + /// + public IEndpoint Endpoint { get; protected set; } + + /// + /// Returns the page context. + /// + public IPageContext PageContext { get; protected set; } + + /// + /// Returns the request. + /// + public Request Request { get; protected set; } + + /// + /// Initializes a new instance of the class. + /// + /// The endpoint associated with the rendering context. + /// >The page context. + /// The request associated with the rendering context. + public TestRenderContext(IEndpoint endpoint, IPageContext pageContext, Request request) + { + Endpoint = endpoint; + PageContext = pageContext; + Request = request; + } + } +} diff --git a/src/WebExpress.WebCore.Test/TestScopeA.cs b/src/WebExpress.WebCore.Test/TestScopeA.cs new file mode 100644 index 0000000..0159221 --- /dev/null +++ b/src/WebExpress.WebCore.Test/TestScopeA.cs @@ -0,0 +1,11 @@ +using WebExpress.WebCore.WebScope; + +namespace WebExpress.WebCore.Test +{ + /// + /// Test scope B implementing the IScope interface. + /// + internal class TestScopeB : IScope + { + } +} \ No newline at end of file diff --git a/src/WebExpress.WebCore.Test/TestScopeB.cs b/src/WebExpress.WebCore.Test/TestScopeB.cs new file mode 100644 index 0000000..acd08eb --- /dev/null +++ b/src/WebExpress.WebCore.Test/TestScopeB.cs @@ -0,0 +1,11 @@ +using WebExpress.WebCore.WebScope; + +namespace WebExpress.WebCore.Test +{ + /// + /// Test scope B implementing the IScope interface. + /// + internal class TestScopeA : IScope + { + } +} \ No newline at end of file diff --git a/src/WebExpress.WebCore.Test/TestScopeC.cs b/src/WebExpress.WebCore.Test/TestScopeC.cs new file mode 100644 index 0000000..9de524c --- /dev/null +++ b/src/WebExpress.WebCore.Test/TestScopeC.cs @@ -0,0 +1,11 @@ +using WebExpress.WebCore.WebScope; + +namespace WebExpress.WebCore.Test +{ + /// + /// Test scope C implementing the IScope interface. + /// + internal class TestScopeC : IScope + { + } +} \ No newline at end of file diff --git a/src/WebExpress.WebCore.Test/TestScopeD.cs b/src/WebExpress.WebCore.Test/TestScopeD.cs new file mode 100644 index 0000000..c060723 --- /dev/null +++ b/src/WebExpress.WebCore.Test/TestScopeD.cs @@ -0,0 +1,11 @@ +using WebExpress.WebCore.WebScope; + +namespace WebExpress.WebCore.Test +{ + /// + /// Test scope D implementing the IScope interface. + /// + internal class TestScopeD : IScope + { + } +} \ No newline at end of file diff --git a/src/WebExpress.WebCore.Test/TestSectionA.cs b/src/WebExpress.WebCore.Test/TestSectionA.cs new file mode 100644 index 0000000..42bbde3 --- /dev/null +++ b/src/WebExpress.WebCore.Test/TestSectionA.cs @@ -0,0 +1,11 @@ +using WebExpress.WebCore.WebSection; + +namespace WebExpress.WebCore.Test +{ + /// + /// Test section A implementing the ISection interface. + /// + public class TestSectionA : ISection + { + } +} \ No newline at end of file diff --git a/src/WebExpress.WebCore.Test/TestStatusPage301.cs b/src/WebExpress.WebCore.Test/TestStatusPage301.cs new file mode 100644 index 0000000..ad41698 --- /dev/null +++ b/src/WebExpress.WebCore.Test/TestStatusPage301.cs @@ -0,0 +1,50 @@ +using WebExpress.WebCore.WebAttribute; +using WebExpress.WebCore.WebMessage; +using WebExpress.WebCore.WebPage; +using WebExpress.WebCore.WebStatusPage; + +namespace WebExpress.WebCore.Test +{ + /// + /// A dummy class for testing purposes. + /// + [Title("webindex:homepage.label")] + [StatusResponse()] + // [Icon("/webexpress/icon.png")] test empty icon + public sealed class TestStatusPage301 : IStatusPage + { + /// + /// Initialization of the status page. Here, for example, managed resources can be loaded. + /// + /// The context of the status page. + private TestStatusPage301(IStatusPageContext statusPageContext) + { + // test the injection + if (statusPageContext == null) + { + throw new ArgumentNullException(nameof(statusPageContext), "Parameter cannot be null or empty."); + } + } + + /// + /// Processing of the status page. + /// + /// The context for rendering the status page. + /// The visual tree to be rendered. + public void Process(IRenderContext renderContext, VisualTree visualTree) + { + // test the parameter + if (renderContext == null) + { + throw new ArgumentNullException(nameof(renderContext), "Parameter cannot be null or empty."); + } + } + + /// + /// Release of unmanaged resources reserved during use. + /// + public void Dispose() + { + } + } +} diff --git a/src/WebExpress.WebCore.Test/TestStatusPage400.cs b/src/WebExpress.WebCore.Test/TestStatusPage400.cs new file mode 100644 index 0000000..c005266 --- /dev/null +++ b/src/WebExpress.WebCore.Test/TestStatusPage400.cs @@ -0,0 +1,67 @@ +using WebExpress.WebCore.WebAttribute; +using WebExpress.WebCore.WebHtml; +using WebExpress.WebCore.WebMessage; +using WebExpress.WebCore.WebPage; +using WebExpress.WebCore.WebStatusPage; + +namespace WebExpress.WebCore.Test +{ + /// + /// A dummy class for testing purposes. + /// + [Title("webindex:homepage.label")] + [StatusResponse()] + [Icon("/webexpress/icon.png")] + public sealed class TestStatusPage400 : IStatusPage + { + /// + /// Returns or sets the status message. + /// + public string StatusMessage { get; private set; } + + /// + /// Initialization of the status page. Here, for example, managed resources can be loaded. + /// + /// The context of the status page. + /// The status message. + private TestStatusPage400(IStatusPageContext statusPageContext, StatusMessage message) + { + // test the injection + if (statusPageContext == null) + { + throw new ArgumentNullException(nameof(statusPageContext), "Parameter cannot be null or empty."); + } + + // test the injection + if (message == null) + { + throw new ArgumentNullException(nameof(message), "Parameter cannot be null or empty."); + } + + StatusMessage = message?.Message; + } + + /// + /// Processing of the status page. + /// + /// The context for rendering the status page. + /// The visual tree to be rendered. + public void Process(IRenderContext renderContext, VisualTree visualTree) + { + // test the parameter + if (renderContext == null) + { + throw new ArgumentNullException(nameof(renderContext), "Parameter cannot be null or empty."); + } + + visualTree.Content = new HtmlText(StatusMessage); + } + + /// + /// Release of unmanaged resources reserved during use. + /// + public void Dispose() + { + } + } +} diff --git a/src/WebExpress.WebCore.Test/TestStatusPage404.cs b/src/WebExpress.WebCore.Test/TestStatusPage404.cs new file mode 100644 index 0000000..9d0504d --- /dev/null +++ b/src/WebExpress.WebCore.Test/TestStatusPage404.cs @@ -0,0 +1,50 @@ +using WebExpress.WebCore.WebAttribute; +using WebExpress.WebCore.WebMessage; +using WebExpress.WebCore.WebPage; +using WebExpress.WebCore.WebStatusPage; + +namespace WebExpress.WebCore.Test +{ + /// + /// A dummy class for testing purposes. + /// + [Title("webindex:homepage.label")] + [StatusResponse()] + [Icon("/webexpress/icon.png")] + public sealed class TestStatusPage404 : IStatusPage + { + /// + /// Initialization of the status page. Here, for example, managed resources can be loaded. + /// + /// The context of the status page. + private TestStatusPage404(IStatusPageContext statusPageContext) + { + // test the injection + if (statusPageContext == null) + { + throw new ArgumentNullException(nameof(statusPageContext), "Parameter cannot be null or empty."); + } + } + + /// + /// Processing of the status page. + /// + /// The context for rendering the status page. + /// The visual tree to be rendered. + public void Process(IRenderContext renderContext, VisualTree visualTree) + { + // test the parameter + if (renderContext == null) + { + throw new ArgumentNullException(nameof(renderContext), "Parameter cannot be null or empty."); + } + } + + /// + /// Release of unmanaged resources reserved during use. + /// + public void Dispose() + { + } + } +} diff --git a/src/WebExpress.WebCore.Test/TestStatusPage500.cs b/src/WebExpress.WebCore.Test/TestStatusPage500.cs new file mode 100644 index 0000000..f283612 --- /dev/null +++ b/src/WebExpress.WebCore.Test/TestStatusPage500.cs @@ -0,0 +1,67 @@ +using WebExpress.WebCore.WebAttribute; +using WebExpress.WebCore.WebHtml; +using WebExpress.WebCore.WebMessage; +using WebExpress.WebCore.WebPage; +using WebExpress.WebCore.WebStatusPage; + +namespace WebExpress.WebCore.Test +{ + /// + /// A dummy class for testing purposes. + /// + [Title("webindex:homepage.label")] + [StatusResponse()] + [Icon("/webexpress/icon.png")] + public sealed class TestStatusPage500 : IStatusPage + { + /// + /// Returns or sets the status message. + /// + public string StatusMessage { get; private set; } + + /// + /// Initialization of the status page. Here, for example, managed resources can be loaded. + /// + /// The context of the status page. + /// The status message. + private TestStatusPage500(IStatusPageContext statusPageContext, StatusMessage message) + { + // test the injection + if (statusPageContext == null) + { + throw new ArgumentNullException(nameof(statusPageContext), "Parameter cannot be null or empty."); + } + + // test the injection + if (message == null) + { + throw new ArgumentNullException(nameof(message), "Parameter cannot be null or empty."); + } + + StatusMessage = message?.Message; + } + + /// + /// Processing of the status page. + /// + /// The context for rendering the status page. + /// The visual tree to be rendered. + public void Process(IRenderContext renderContext, VisualTree visualTree) + { + // test the parameter + if (renderContext == null) + { + throw new ArgumentNullException(nameof(renderContext), "Parameter cannot be null or empty."); + } + + visualTree.Content = new HtmlText(StatusMessage); + } + + /// + /// Release of unmanaged resources reserved during use. + /// + public void Dispose() + { + } + } +} diff --git a/src/WebExpress.WebCore.Test/TestTask.cs b/src/WebExpress.WebCore.Test/TestTask.cs new file mode 100644 index 0000000..bfb27a3 --- /dev/null +++ b/src/WebExpress.WebCore.Test/TestTask.cs @@ -0,0 +1,36 @@ +using WebExpress.WebCore.WebComponent; + +namespace WebExpress.WebCore.Test +{ + /// + /// Represents a test task that implements the Task. + /// + public class TestTask : WebTask.Task + { + /// + /// Initializes a new instance of the class. + /// + /// The component hub used for dependency injection. + /// The unique identifier for the task. + /// The arguments for the task. + public TestTask(IComponentHub componentHub, string id, params object[] args) + : base(id, args) + { + // test the injection + if (componentHub == null) + { + throw new ArgumentNullException(nameof(componentHub), "Parameter cannot be null or empty."); + } + + if (id == null) + { + throw new ArgumentNullException(nameof(id), "Parameter cannot be null or empty."); + } + + if (args == null) + { + throw new ArgumentNullException(nameof(args), "Parameter cannot be null or empty."); + } + } + } +} diff --git a/src/WebExpress.WebCore.Test/TestThemeA.cs b/src/WebExpress.WebCore.Test/TestThemeA.cs new file mode 100644 index 0000000..e1506aa --- /dev/null +++ b/src/WebExpress.WebCore.Test/TestThemeA.cs @@ -0,0 +1,24 @@ +using WebExpress.WebCore.WebAttribute; +using WebExpress.WebCore.WebTheme; + +namespace WebExpress.WebCore.Test +{ + /// + /// A dummy theme for testing. + /// + [Name("TestThemeA")] + [Description("A dummy theme for testing.")] + [Image("webexpress.webcore.test.testthemea.png")] + [ThemeMode(ThemeMode.Dark)] + [ThemeStyle("/asserts/css/themea.css")] + public sealed class TestThemeA : ITheme + { + /// + /// Returns the text color for the theme. + /// + /// + /// A string representing the text color in hexadecimal format. + /// + public static string TextColor => "FFFFFF"; + } +} diff --git a/src/WebExpress.WebCore.Test/TestThemeB.cs b/src/WebExpress.WebCore.Test/TestThemeB.cs new file mode 100644 index 0000000..41229a9 --- /dev/null +++ b/src/WebExpress.WebCore.Test/TestThemeB.cs @@ -0,0 +1,20 @@ +using WebExpress.WebCore.WebAttribute; +using WebExpress.WebCore.WebTheme; + +namespace WebExpress.WebCore.Test +{ + /// + /// A dummy theme for testing. + /// + [Name("TestThemeB")] + public sealed class TestThemeB : ITheme + { + /// + /// Returns the text color for the theme. + /// + /// + /// A string representing the text color in hexadecimal format. + /// + public static string TextColor => "000000"; + } +} diff --git a/src/WebExpress.WebCore.Test/TestVisualTree.cs b/src/WebExpress.WebCore.Test/TestVisualTree.cs new file mode 100644 index 0000000..31f2d17 --- /dev/null +++ b/src/WebExpress.WebCore.Test/TestVisualTree.cs @@ -0,0 +1,116 @@ +using WebExpress.WebCore.Internationalization; +using WebExpress.WebCore.WebHtml; +using WebExpress.WebCore.WebPage; + +namespace WebExpress.WebCore.Test +{ + /// + /// A custom visual tree for testing purposes. + /// + public class TestVisualTree : IVisualTree + { + /// + /// Returns the title of the html document. + /// + public string Title { get; set; } + + /// + /// Returns the favicons. + /// + public List Favicons { get; } = []; + + /// + /// Returns the internal stylesheet. + /// + public List Styles { get; } = []; + + /// + /// Returns the links to the java script files to be used, which are inserted in the header. + /// + public List HeaderScriptLinks { get; } = []; + + /// + /// Returns the links to the java script files to be used. + /// + public List ScriptLinks { get; } = []; + + /// + /// Returns the links to the java script files to be used, which are inserted in the header. + /// + public List HeaderScripts { get; } = []; + + /// + /// Returns the links to the java script files to be used. + /// + public IDictionary Scripts { get; } = new Dictionary(); + + /// + /// Returns the links to the css files to be used. + /// + public List CssLinks { get; } = []; + + /// + /// Returns the meta information. + /// + public List> Meta { get; } = []; + + /// + /// Returns or sets the content. + /// + public IHtmlNode Content { get; set; } + + /// + /// Initializes a new instance of the class. + /// + public TestVisualTree() + { + } + + /// + /// Adds or replaces a java script if it exists. + /// + /// The key. + /// The java script code. + public virtual void AddScript(string key, string code) + { + } + + /// + /// Adds a java script. + /// + /// The link of the java script file. + public virtual void AddScriptLink(string url) + { + } + + /// + /// Adds a java script in the header. + /// + /// The link of the java script file. + public virtual void AddHeaderScriptLinks(string url) + { + } + + /// + /// Convert to html. + /// + /// The context for rendering the visual tree. + /// The page as an html tree. + public virtual IHtmlNode Render(IVisualTreeContext context) + { + var html = new HtmlElementRootHtml(); + html.Head.Title = I18N.Translate(context.Request, Title); + html.Head.Favicons = Favicons?.Select(x => new Favicon(x.Url, x.Mediatype)); + html.Head.Styles = Styles; + html.Head.Meta = Meta; + html.Head.Scripts = HeaderScripts; + html.Body.Add(Content); + html.Body.Scripts = [.. Scripts.Values]; + + html.Head.CssLinks = CssLinks.Where(x => x != null).Select(x => x.ToString()); + html.Head.ScriptLinks = HeaderScriptLinks?.Where(x => x != null).Select(x => x.ToString()); + + return html; + } + } +} diff --git a/src/WebExpress.WebCore.Test/Uri/UnitTestUri.cs b/src/WebExpress.WebCore.Test/Uri/UnitTestUri.cs new file mode 100644 index 0000000..6417065 --- /dev/null +++ b/src/WebExpress.WebCore.Test/Uri/UnitTestUri.cs @@ -0,0 +1,191 @@ +using WebExpress.WebCore.WebEndpoint; +using WebExpress.WebCore.WebUri; + +namespace WebExpress.WebCore.Test.Uri +{ + /// + /// Tests an uri. + /// + [Collection("NonParallelTests")] + public class UnitTestUri + { + /// + /// Test the constructor with absolute URIs. + /// + [Theory] + [InlineData(UriScheme.Http, "www.example.com", null, null, null, "a=1&b=2", "fragment", "http://www.example.com/?a=1&b=2#fragment")] + [InlineData(UriScheme.Http, "www.example.com", null, null, "", "a=1&b=2", "fragment", "http://www.example.com/?a=1&b=2#fragment")] + [InlineData(UriScheme.Http, "www.example.com", null, null, "/abc", "a=1&b=2", "fragment", "http://www.example.com/abc?a=1&b=2#fragment")] + [InlineData(UriScheme.Http, "example.com", "user", "8080", "/abc", "a=1&b=2", "fragment", "http://user@example.com:8080/abc?a=1&b=2#fragment")] + [InlineData(UriScheme.Http, "example", null, null, "/assets/img/example.svg", null, null, "http://example/assets/img/example.svg")] + [InlineData(UriScheme.Http, "localhost", null, null, null, null, null, "http://localhost/")] + [InlineData(UriScheme.Http, "localhost", null, null, "/", null, null, "http://localhost/")] + [InlineData(UriScheme.Http, "example.com", "user", "80", "/abc", "a=1&b=2", "fragment", "http://user@example.com/abc?a=1&b=2#fragment")] + public void UriAbsolute(UriScheme scheme, string authority, string user, string port, string path, string query, string fragment, string expected) + { + // preconditions + var uriUser = user != null ? user + "@" : ""; + var uriPort = port != null ? ":" + port : null; + var uriQuery = query != null ? "?" + query : ""; + var uriFragment = fragment != null ? "#" + fragment : null; + + // test execution + var uri = new UriEndpoint($"{scheme}://{uriUser}{authority}{uriPort}{path}{uriQuery}{uriFragment}"); + + Assert.Equal(expected, uri.ToString()); + Assert.Equal(scheme, uri.Scheme); + Assert.Equal(authority, uri.Authority.Host); + Assert.Equal(path, !string.IsNullOrWhiteSpace(path) + ? "/" + string.Join("/", uri.PathSegments.Skip(1)) + : path); + Assert.Equal(query, uri.Query.Any() ? string.Join("&", uri.Query) : null); + Assert.Equal(fragment, uri.Fragment); + } + + /// + /// Test the constructor with relative URIs. + /// + [Theory] + [InlineData(null, null, null, "/")] + [InlineData(null, "a=1&b=2", null, "/?a=1&b=2")] + [InlineData(null, null, "fragment", "/#fragment")] + [InlineData(null, "a=1&b=2", "fragment", "/?a=1&b=2#fragment")] + [InlineData("", null, null, "/")] + [InlineData("", "a=1&b=2", null, "/?a=1&b=2")] + [InlineData("", null, "fragment", "/#fragment")] + [InlineData("", "a=1&b=2", "fragment", "/?a=1&b=2#fragment")] + [InlineData("/", null, null, "/")] + [InlineData("/", "a=1&b=2", null, "/?a=1&b=2")] + [InlineData("/abc", "a=1&b=2", "fragment", "/abc?a=1&b=2#fragment")] + [InlineData("/assets/img/example.svg", null, null, "/assets/img/example.svg")] + public void UriRelative(string path, string query, string fragment, string expected) + { + // preconditions + var uriQuery = query != null ? "?" + query : ""; + var uriFragment = fragment != null ? "#" + fragment : null; + + // test execution + var uri = new UriEndpoint($"{path}{uriQuery}{uriFragment}"); + + Assert.Equal(expected, uri.ToString()); + Assert.Equal(path, !string.IsNullOrWhiteSpace(path) + ? "/" + string.Join("/", uri.PathSegments.Skip(1)) + : path); + Assert.Equal(query, uri.Query.Any() ? string.Join("&", uri.Query) : null); + Assert.Equal(fragment, uri.Fragment); + } + + /// + /// Test the concat method. + /// + [Theory] + [InlineData("/a/b/c", null, "/a/b/c", 4)] + [InlineData("/a/b/c", "", "/a/b/c", 4)] + [InlineData("/a/b/c", "d", "/a/b/c/d", 5)] + [InlineData("/a/b/c", "/d/e/f", "/a/b/c/d/e/f", 7)] + public void Concat(string path, string segment, string expected, int count) + { + // preconditions + var uri = new UriEndpoint(path); + + // test execution + var concat = uri.Concat(segment); + + Assert.Equal(expected, concat.ToString()); + Assert.Equal(count, concat.PathSegments.Count()); + } + + /// + /// Test the skip method. + /// + [Theory] + [InlineData("/a/b/c", 0, "/a/b/c")] + [InlineData("/a/b/c", 1, "/a/b/c")] + [InlineData("/a/b/c", 2, "/b/c")] + [InlineData("/a/b/c", 3, "/c")] + [InlineData("/a/b/c", 4, null)] + [InlineData("/a/b/c", 5, null)] + public void Skip(string path, int skipCount, string expected) + { + // preconditions + var uri = new UriEndpoint(path); + + // test execution + var skip = uri.Skip(skipCount); + + Assert.Equal(expected, skip?.ToString()); + } + + /// + /// Test the take method. + /// + [Theory] + [InlineData("/a/b/c", 0, "/")] + [InlineData("/a/b/c", 1, "/")] + [InlineData("/a/b/c", 2, "/a")] + [InlineData("/a/b/c", 3, "/a/b")] + [InlineData("/a/b/c", 4, "/a/b/c")] + [InlineData("/a/b/c", 5, "/a/b/c")] + [InlineData("/a/b/c", -1, "/a/b")] + [InlineData("/a/b/c", -2, "/a")] + [InlineData("/a/b/c", -3, "/")] + [InlineData("/a/b/c", -4, null)] + [InlineData("/a/b/c", -5, null)] + public void Take(string path, int takeCount, string expected) + { + // preconditions + var uri = new UriEndpoint(path); + + // test execution + var take = uri.Take(takeCount); + + Assert.Equal(expected, take?.ToString()); + } + + /// + /// Test the merge method. + /// + [Theory] + [InlineData("http://www.example.com", "/a/b/c", "http://www.example.com/a/b/c")] + [InlineData("http://www.example.com/", "/a/b/c", "http://www.example.com/a/b/c")] + [InlineData("http://www.example.com/a/b/c", "/a/b/c", "http://www.example.com/a/b/c")] + [InlineData("http://www.example.com/a/$guid/c", "/a/$guid/c", "http://www.example.com/a/$guid/c")] + public void Merge(string uri, string route, string expected) + { + // preconditions + var random = Guid.NewGuid().ToString(); + var uriEndpoint = new UriEndpoint(uri.Replace("$guid", random)); + var routeEndpoint = new RouteEndpoint + ( + [.. route.Split('/').Select + ( + x => (IUriPathSegment)(x == "$guid" + ? new UriPathSegmentVariableGuid("guid") { Value = random } + : new UriPathSegmentConstant(x)) + )] + ); + + // test execution + var resourceUri = new UriEndpoint(uriEndpoint, routeEndpoint.PathSegments); + + Assert.Equal(expected.Replace("$guid", random), resourceUri?.ToString()); + } + + /// + /// Test the base path property. + /// + [Theory] + [InlineData("http://user@example.com/x/y/z", "http://user@example.com/x", "http://user@example.com/x")] + [InlineData("http://user@example.com/a/b/c/x/y/z", "http://user@example.com/a/b/c", "http://user@example.com/a/b/c")] + public void BasePath(string uri, string baseUri, string expected) + { + var resourceUri = new UriEndpoint(uri) + { + BasePath = new UriEndpoint(baseUri) + }; + + Assert.Equal(uri, resourceUri.ToString()); + Assert.Equal(expected, resourceUri.BasePath.ToString()); + } + } +} diff --git a/src/WebExpress.WebCore.Test/Uri/UnitTestUriAbsolute.cs b/src/WebExpress.WebCore.Test/Uri/UnitTestUriAbsolute.cs deleted file mode 100644 index 175552a..0000000 --- a/src/WebExpress.WebCore.Test/Uri/UnitTestUriAbsolute.cs +++ /dev/null @@ -1,139 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using WebExpress.WebCore.WebUri; -using Xunit; - -namespace WebExpress.WebCore.Test.Uri -{ - /// - /// Tests an absolute Uri. - /// - public class UnitTestUriAbsolute - { - [Fact] - public void Test_0() - { - var str = "http://user@example.com:8080/abc#a?b=1&c=2"; - var uri = new UriResource(str); - - Assert.True - ( - uri.ToString() == str && - uri.Scheme == UriScheme.Http && - uri.Authority.User == "user" && - uri.Authority.Host == "example.com" && - uri.Authority.Port == 8080 && - uri.Fragment == "a" && - uri.Query.FirstOrDefault()?.Key == "b" && - uri.Query.FirstOrDefault()?.Value == "1" && - uri.Query.LastOrDefault()?.Key == "c" && - uri.Query.LastOrDefault()?.Value == "2" && - uri.IsRelative == false - ); - } - - [Fact] - public void Test_1() - { - var str = "http://vila/assets/img/vila.svg"; - var uri = new UriResource(str); - - Assert.True - ( - uri.ToString() == str && - uri.Scheme == UriScheme.Http && - uri.Authority.User == null && - uri.Authority.Host == "vila" && - uri.Authority.Port == null && - uri.Fragment == null && - uri.Query.Any() == false && - uri.IsRelative == false - ); - } - - [Fact] - public void Test_2() - { - var str = "http://localhost"; - var uri = new UriResource(str); - - Assert.True - ( - uri.ToString() == str + "/" && - uri.Scheme == UriScheme.Http && - uri.Authority.User == null && - uri.Authority.Host == "localhost" && - uri.Authority.Port == null && - uri.Fragment == null && - uri.Query.Any() == false && - uri.IsRelative == false - ); - } - - [Fact] - public void Test_3() - { - var str = "http://user@example.com:80/abc#a?b=1&c=2"; - var uri = new UriResource(str); - - Assert.True - ( - uri.ToString() == "http://user@example.com/abc#a?b=1&c=2" && - uri.Scheme == UriScheme.Http && - uri.Authority.User == "user" && - uri.Authority.Host == "example.com" && - uri.Authority.Port == 80 && - uri.Fragment == "a" && - uri.Query.FirstOrDefault()?.Key == "b" && - uri.Query.FirstOrDefault()?.Value == "1" && - uri.Query.LastOrDefault()?.Key == "c" && - uri.Query.LastOrDefault()?.Value == "2" && - uri.IsRelative == false - ); - } - - [Fact] - public void Test_4() - { - var str = "http://user@example.com:80/abc#a?b=1&c=2"; - var uri = new UriResource(str); - var segments = new List - { - new UriPathSegmentRoot(), - new UriPathSegmentConstant("a"), - new UriPathSegmentConstant("b"), - new UriPathSegmentConstant("c") - }; - - var extendetSegments = new List - { - new UriPathSegmentRoot(), - new UriPathSegmentConstant("x"), - new UriPathSegmentConstant("y") - }; - - var resourceUri = new UriResource(uri, segments); - resourceUri = new UriResource(resourceUri, resourceUri.PathSegments, extendetSegments); - resourceUri.ServerRoot = new UriResource("http://user@example.com:80"); - resourceUri.ApplicationRoot = new UriResource("http://user@example.com:80"); - resourceUri.ModuleRoot = new UriResource("http://user@example.com:80"); - resourceUri.ResourceRoot = new UriResource("http://user@example.com:80/abc"); - - Assert.True - ( - resourceUri.ToString() == "http://user@example.com/a/b/c/x/y#a?b=1&c=2" && - resourceUri.Scheme == UriScheme.Http && - resourceUri.Authority.User == "user" && - resourceUri.Authority.Host == "example.com" && - resourceUri.Authority.Port == 80 && - resourceUri.Fragment == "a" && - resourceUri.Query.FirstOrDefault()?.Key == "b" && - resourceUri.Query.FirstOrDefault()?.Value == "1" && - resourceUri.Query.LastOrDefault()?.Key == "c" && - resourceUri.Query.LastOrDefault()?.Value == "2" && - resourceUri.ServerRoot != null && - resourceUri.IsRelative == false - ); - } - } -} diff --git a/src/WebExpress.WebCore.Test/Uri/UnitTestUriRelative.cs b/src/WebExpress.WebCore.Test/Uri/UnitTestUriRelative.cs deleted file mode 100644 index fdc2ee6..0000000 --- a/src/WebExpress.WebCore.Test/Uri/UnitTestUriRelative.cs +++ /dev/null @@ -1,105 +0,0 @@ -using System.Linq; -using WebExpress.WebCore.WebUri; -using Xunit; - -namespace WebExpress.WebCore.Test.Uri -{ - /// - /// Tests an relative Uri. - /// - public class UnitTestUriRelative - { - [Fact] - public void Test_0() - { - var str = "/abc#a?b=1&c=2"; - var uri = new UriResource(str); - - Assert.True - ( - uri.ToString() == str && - uri.Scheme == UriScheme.Http && - uri.PathSegments.Count == 2 && - uri.Fragment == "a" && - uri.Query.FirstOrDefault()?.Key == "b" && - uri.Query.FirstOrDefault()?.Value == "1" && - uri.Query.LastOrDefault()?.Key == "c" && - uri.Query.LastOrDefault()?.Value == "2" && - uri.IsRelative - ); - } - - [Fact] - public void Test_1() - { - var str = "/assets/img/vila.svg"; - var uri = new UriResource("/assets/img/vila.svg"); - - Assert.True - ( - uri.ToString() == str && - uri.Scheme == UriScheme.Http && - uri.PathSegments.Count == 4 && - uri.Fragment == null && - uri.Query.Any() == false && - uri.IsRelative - ); - } - - [Fact] - public void Test_2() - { - var str = "/"; - var uri = new UriResource(str); - - Assert.True - ( - uri.ToString() == str && - uri.Scheme == UriScheme.Http && - uri.Authority == null && - uri.PathSegments.Count == 1 && - uri.Fragment == null && - uri.Query.Any() == false && - uri.IsRelative - ); - } - - [Fact] - public void Test_3() - { - var str = "/?b=1&c=2"; - var uri = new UriResource(str); - - Assert.True - ( - uri.ToString() == str && - uri.Scheme == UriScheme.Http && - uri.Authority == null && - uri.PathSegments.Count == 1 && - uri.Query.FirstOrDefault()?.Key == "b" && - uri.Query.FirstOrDefault()?.Value == "1" && - uri.Query.LastOrDefault()?.Key == "c" && - uri.Query.LastOrDefault()?.Value == "2" && - uri.IsRelative - ); - } - - [Fact] - public void Test_4() - { - var str = ""; - var uri = new UriResource(str); - - Assert.True - ( - uri.ToString() == str + "/" && - uri.Scheme == UriScheme.Http && - uri.Authority == null && - uri.PathSegments.Count == 1 && - uri.Fragment == null && - uri.Query.Any() == false && - uri.IsRelative - ); - } - } -} diff --git a/src/WebExpress.WebCore.Test/Uri/UnitTestUriRelativeAppend.cs b/src/WebExpress.WebCore.Test/Uri/UnitTestUriRelativeAppend.cs deleted file mode 100644 index 1dc41bc..0000000 --- a/src/WebExpress.WebCore.Test/Uri/UnitTestUriRelativeAppend.cs +++ /dev/null @@ -1,49 +0,0 @@ -using WebExpress.WebCore.WebUri; -using Xunit; - -namespace WebExpress.WebCore.Test.Uri -{ - /// - /// Tests the append method. - /// - public class UnitTestUriRelativeAppend - { - private readonly UriResource Uri = new UriResource("/a/b/c"); - - [Fact] - public void Append_0() - { - var append = Uri.Append("/d"); - - Assert.True - ( - append.ToString() == "/a/b/c/d" && - append.PathSegments.Count == 5 - ); - } - - [Fact] - public void Append_1() - { - var append = Uri.Append("d"); - - Assert.True - ( - append.ToString() == "/a/b/c/d" && - append.PathSegments.Count == 5 - ); - } - - [Fact] - public void Append_2() - { - var append = Uri.Append("/d/e/f"); - - Assert.True - ( - append.ToString() == "/a/b/c/d/e/f" && - append.PathSegments.Count == 7 - ); - } - } -} diff --git a/src/WebExpress.WebCore.Test/Uri/UnitTestUriRelativeExtendedPath.cs b/src/WebExpress.WebCore.Test/Uri/UnitTestUriRelativeExtendedPath.cs deleted file mode 100644 index 843c0f3..0000000 --- a/src/WebExpress.WebCore.Test/Uri/UnitTestUriRelativeExtendedPath.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System.Collections.Generic; -using WebExpress.WebCore.WebUri; -using Xunit; - -namespace WebExpress.WebCore.Test.Uri -{ - /// - /// Tests the extended path property. - /// - public class UnitTestUriRelativeExtendedPath - { - private readonly UriResource Uri = new UriResource("http://user@example.com:80"); - - [Fact] - public void ExtendedPath_0() - { - var segments = new List - { - new UriPathSegmentRoot(), - new UriPathSegmentConstant("a"), - new UriPathSegmentConstant("b"), - new UriPathSegmentConstant("c"), - new UriPathSegmentConstant("x"), - new UriPathSegmentConstant("y") - }; - - var resourceUri = new UriResource(Uri, segments); - resourceUri.ServerRoot = new UriResource("http://user@example.com:80"); - resourceUri.ApplicationRoot = new UriResource("http://user@example.com:80"); - resourceUri.ModuleRoot = new UriResource("http://user@example.com:80"); - resourceUri.ResourceRoot = new UriResource("http://user@example.com:80/a/b/c"); - - Assert.True - ( - resourceUri.ToString() == "http://user@example.com/a/b/c/x/y" && - resourceUri.ExtendedPath.ToString() == "/x/y" && - resourceUri.Scheme == UriScheme.Http && - resourceUri.Authority.User == "user" && - resourceUri.Authority.Host == "example.com" && - resourceUri.Authority.Port == 80 && - resourceUri.ServerRoot != null && - resourceUri.IsRelative == false - ); - } - } -} diff --git a/src/WebExpress.WebCore.Test/Uri/UnitTestUriRelativeSkip.cs b/src/WebExpress.WebCore.Test/Uri/UnitTestUriRelativeSkip.cs deleted file mode 100644 index c70f292..0000000 --- a/src/WebExpress.WebCore.Test/Uri/UnitTestUriRelativeSkip.cs +++ /dev/null @@ -1,79 +0,0 @@ -using WebExpress.WebCore.WebUri; -using Xunit; - -namespace WebExpress.WebCore.Test.Uri -{ - /// - /// Tests the skip method. - /// - public class UnitTestUriRelativeSkip - { - private readonly UriResource Uri = new UriResource("/a/b/c"); - - [Fact] - public void Skip_0() - { - var skip = Uri.Skip(0); - - Assert.True - ( - skip.ToString().Equals("/a/b/c") && skip.PathSegments.Count == 4 - ); - } - - [Fact] - public void Skip_1() - { - var skip = Uri.Skip(1); - - Assert.True - ( - skip.ToString().Equals("/a/b/c") && skip.PathSegments.Count == 3 - ); - } - - [Fact] - public void Skip_2() - { - var skip = Uri.Skip(2); - - Assert.True - ( - skip.ToString().Equals("/b/c") && skip.PathSegments.Count == 2 - ); - } - - [Fact] - public void Skip_3() - { - var skip = Uri.Skip(3); - - Assert.True - ( - skip.ToString().Equals("/c") && skip.PathSegments.Count == 1 - ); - } - - [Fact] - public void Skip_4() - { - var skip = Uri.Skip(4); - - Assert.True - ( - skip == null - ); - } - - [Fact] - public void Skip_5() - { - var skip = new UriResource().Skip(1); - - Assert.True - ( - skip == null - ); - } - } -} diff --git a/src/WebExpress.WebCore.Test/Uri/UnitTestUriRelativeTake.cs b/src/WebExpress.WebCore.Test/Uri/UnitTestUriRelativeTake.cs deleted file mode 100644 index fd56061..0000000 --- a/src/WebExpress.WebCore.Test/Uri/UnitTestUriRelativeTake.cs +++ /dev/null @@ -1,134 +0,0 @@ -using WebExpress.WebCore.WebUri; -using Xunit; - -namespace WebExpress.WebCore.Test.Uri -{ - /// - /// Tests the take method. - /// - public class UnitTestUriRelativeTake - { - private readonly UriResource Uri = new UriResource("/a/b/c"); - - [Fact] - public void Take_0() - { - var take = Uri.Take(0); - - Assert.True - ( - take.ToString().Equals("/") && take.PathSegments.Count == 0 - ); - } - - [Fact] - public void Take_1() - { - var take = Uri.Take(1); - - Assert.True - ( - take.ToString().Equals("/") && take.PathSegments.Count == 1 - ); - } - - [Fact] - public void Take_2() - { - var take = Uri.Take(2); - - Assert.True - ( - take.ToString().Equals("/a") && take.PathSegments.Count == 2 - ); - } - - [Fact] - public void Take_3() - { - var take = Uri.Take(3); - - Assert.True - ( - take.ToString().Equals("/a/b") && take.PathSegments.Count == 3 - ); - } - - [Fact] - public void Take_4() - { - var take = Uri.Take(4); - - Assert.True - ( - take.ToString().Equals("/a/b/c") && take.PathSegments.Count == 4 - ); - } - - [Fact] - public void Take_5() - { - var take = Uri.Take(5); - - Assert.True - ( - take.ToString().Equals("/a/b/c") && take.PathSegments.Count == 4 - ); - } - - [Fact] - public void Take_6() - { - var take = Uri.Take(-1); - - Assert.True - ( - take.ToString().Equals("/a/b") && take.PathSegments.Count == 3 - ); - } - - [Fact] - public void Take_7() - { - var take = Uri.Take(-2); - - Assert.True - ( - take.ToString().Equals("/a") && take.PathSegments.Count == 2 - ); - } - - [Fact] - public void Take_8() - { - var take = Uri.Take(-3); - - Assert.True - ( - take.ToString().Equals("/") && take.PathSegments.Count == 1 - ); - } - - [Fact] - public void Take_9() - { - var take = Uri.Take(-4); - - Assert.True - ( - take == null - ); - } - - [Fact] - public void Take_10() - { - var take = Uri.Take(-5); - - Assert.True - ( - take == null - ); - } - } -} diff --git a/src/WebExpress.WebCore.Test/WWW/About.cs b/src/WebExpress.WebCore.Test/WWW/About.cs new file mode 100644 index 0000000..a3326d4 --- /dev/null +++ b/src/WebExpress.WebCore.Test/WWW/About.cs @@ -0,0 +1,59 @@ +using WebExpress.WebCore.WebAttribute; +using WebExpress.WebCore.WebPage; +using WebExpress.WebCore.WebScope; + +namespace WebExpress.WebCore.Test.WWW +{ + /// + /// A dummy class for testing purposes. + /// + [Title("webindex:about.label")] + public sealed class About : IPage, IScope + { + /// + /// Returns or sets the title of the page. + /// + public string Title { get; set; } + + /// + /// Returns or sets the page context. + /// + public IPageContext PageContext { get; private set; } + + /// + /// Initialization of the page. Here, for example, managed resources can be loaded. + /// + /// The context of the page. + public About(IPageContext pageContext) + { + PageContext = pageContext; + + // test the injection + if (pageContext == null) + { + throw new ArgumentNullException(nameof(pageContext), "Parameter cannot be null or empty."); + } + } + + /// + /// Processing of the page. + /// + /// The context for rendering the page. + /// The visual tree to be rendered. + public void Process(IRenderContext renderContext, VisualTree visualTree) + { + // test the context + if (renderContext == null) + { + throw new ArgumentNullException(nameof(renderContext), "Parameter cannot be null or empty."); + } + } + + /// + /// Release of unmanaged resources reserved during use. + /// + public void Dispose() + { + } + } +} diff --git a/src/WebExpress.WebCore.Test/WWW/Api/1/TestRestApiA.cs b/src/WebExpress.WebCore.Test/WWW/Api/1/TestRestApiA.cs new file mode 100644 index 0000000..3c4f47c --- /dev/null +++ b/src/WebExpress.WebCore.Test/WWW/Api/1/TestRestApiA.cs @@ -0,0 +1,78 @@ +using WebExpress.WebCore.WebAttribute; +using WebExpress.WebCore.WebRestApi; + +namespace WebExpress.WebCore.Test.WWW.Api._1 +{ + /// + /// A dummy class for testing purposes. + /// + [Method(CrudMethod.POST)] + [Method(CrudMethod.GET)] + public sealed class TestRestApiA : IRestApi + { + /// + /// Initialization of the rest api resource. Here, for example, managed resources can be loaded. + /// + /// The context of the restapi resource. + public TestRestApiA(IRestApiContext restApiContext) + { + // test the injection + if (restApiContext == null) + { + throw new ArgumentNullException(nameof(restApiContext), "Parameter cannot be null or empty."); + } + } + + /// + /// Creates data. + /// + /// The request. + public void CreateData(WebMessage.Request request) + { + + } + + /// + /// Gets data. + /// + /// The request. + /// The data. + public object GetData(WebMessage.Request request) + { + return null; + } + + /// + /// Updates data. + /// + /// The request. + public void UpdateData(WebMessage.Request request) + { + // test the request + if (request == null) + { + throw new ArgumentNullException(nameof(request), "Parameter cannot be null or empty."); + } + } + + /// + /// Deletes data. + /// + /// The request. + public void DeleteData(WebMessage.Request request) + { + // test the request + if (request == null) + { + throw new ArgumentNullException(nameof(request), "Parameter cannot be null or empty."); + } + } + + /// + /// Release of unmanaged resources reserved during use. + /// + public void Dispose() + { + } + } +} diff --git a/src/WebExpress.WebCore.Test/WWW/Api/2/TestRestApiB.cs b/src/WebExpress.WebCore.Test/WWW/Api/2/TestRestApiB.cs new file mode 100644 index 0000000..b4d11a8 --- /dev/null +++ b/src/WebExpress.WebCore.Test/WWW/Api/2/TestRestApiB.cs @@ -0,0 +1,77 @@ +using WebExpress.WebCore.WebAttribute; +using WebExpress.WebCore.WebRestApi; + +namespace WebExpress.WebCore.Test.WWW.Api._2 +{ + /// + /// A dummy class for testing purposes. + /// + [Method(CrudMethod.GET)] + public sealed class TestRestApiB : IRestApi + { + /// + /// Initialization of the rest api resource. Here, for example, managed resources can be loaded. + /// + /// The context of the restapi resource. + public TestRestApiB(IRestApiContext restApiContext) + { + // test the injection + if (restApiContext == null) + { + throw new ArgumentNullException(nameof(restApiContext), "Parameter cannot be null or empty."); + } + } + + /// + /// Creates data. + /// + /// The request. + public void CreateData(WebMessage.Request request) + { + + } + + /// + /// Gets data. + /// + /// The request. + /// The data. + public object GetData(WebMessage.Request request) + { + return null; + } + + /// + /// Updates data. + /// + /// The request. + public void UpdateData(WebMessage.Request request) + { + // test the request + if (request == null) + { + throw new ArgumentNullException(nameof(request), "Parameter cannot be null or empty."); + } + } + + /// + /// Deletes data. + /// + /// The request. + public void DeleteData(WebMessage.Request request) + { + // test the request + if (request == null) + { + throw new ArgumentNullException(nameof(request), "Parameter cannot be null or empty."); + } + } + + /// + /// Release of unmanaged resources reserved during use. + /// + public void Dispose() + { + } + } +} diff --git a/src/WebExpress.WebCore.Test/WWW/Api/3/TestRestApiC.cs b/src/WebExpress.WebCore.Test/WWW/Api/3/TestRestApiC.cs new file mode 100644 index 0000000..920d4de --- /dev/null +++ b/src/WebExpress.WebCore.Test/WWW/Api/3/TestRestApiC.cs @@ -0,0 +1,86 @@ +using WebExpress.WebCore.WebAttribute; +using WebExpress.WebCore.WebComponent; +using WebExpress.WebCore.WebRestApi; + +namespace WebExpress.WebCore.Test.WWW.Api._3 +{ + /// + /// A dummy class for testing purposes. + /// + [Method(CrudMethod.GET)] + public sealed class TestRestApiC : RestApi + { + /// + /// Initialization of the rest api resource. Here, for example, managed resources can be loaded. + /// + /// The component hub. + /// The context of the restapi resource. + public TestRestApiC(IComponentHub componentHub, IRestApiContext restApiContext) + : base(restApiContext) + { + // test the injection + if (componentHub == null) + { + throw new ArgumentNullException(nameof(componentHub), "Parameter cannot be null or empty."); + } + + // test the injection + if (restApiContext == null) + { + throw new ArgumentNullException(nameof(restApiContext), "Parameter cannot be null or empty."); + } + } + + /// + /// Creates data. + /// + /// The request. + public override void CreateData(WebMessage.Request request) + { + + } + + /// + /// Gets data. + /// + /// The request. + /// The data. + public override object GetData(WebMessage.Request request) + { + return null; + } + + /// + /// Updates data. + /// + /// The request. + public override void UpdateData(WebMessage.Request request) + { + // test the request + if (request == null) + { + throw new ArgumentNullException(nameof(request), "Parameter cannot be null or empty."); + } + } + + /// + /// Deletes data. + /// + /// The request. + public override void DeleteData(WebMessage.Request request) + { + // test the request + if (request == null) + { + throw new ArgumentNullException(nameof(request), "Parameter cannot be null or empty."); + } + } + + /// + /// Release of unmanaged resources reserved during use. + /// + public override void Dispose() + { + } + } +} diff --git a/src/WebExpress.WebCore.Test/WWW/Blog/Index.cs b/src/WebExpress.WebCore.Test/WWW/Blog/Index.cs new file mode 100644 index 0000000..ae60598 --- /dev/null +++ b/src/WebExpress.WebCore.Test/WWW/Blog/Index.cs @@ -0,0 +1,52 @@ +using WebExpress.WebCore.WebAttribute; +using WebExpress.WebCore.WebPage; + +namespace WebExpress.WebCore.Test.WWW.Blog +{ + /// + /// A dummy class for testing purposes. + /// + [Title("webindex:pagee.label")] + public sealed class Index : Page + { + /// + /// Initialization of the page. Here, for example, managed resources can be loaded. + /// + /// The context of the page. + private Index(IPageContext pageContext) + { + // test the injection + if (pageContext == null) + { + throw new ArgumentNullException(nameof(pageContext), "Parameter cannot be null or empty."); + } + } + + /// + /// Processing of the page. + /// + /// The context for rendering the page. + /// The visual tree to be rendered. + public override void Process(IRenderContext renderContext, TestVisualTree visualTree) + { + // test the context + if (renderContext == null) + { + throw new ArgumentNullException(nameof(renderContext), "Parameter cannot be null or empty."); + } + + // test the visualTree + if (visualTree == null) + { + throw new ArgumentNullException(nameof(visualTree), "Parameter cannot be null or empty."); + } + } + + /// + /// Release of unmanaged resources reserved during use. + /// + public override void Dispose() + { + } + } +} diff --git a/src/WebExpress.WebCore.Test/WWW/Blog/Post/Add.cs b/src/WebExpress.WebCore.Test/WWW/Blog/Post/Add.cs new file mode 100644 index 0000000..6f74a0c --- /dev/null +++ b/src/WebExpress.WebCore.Test/WWW/Blog/Post/Add.cs @@ -0,0 +1,52 @@ +using WebExpress.WebCore.WebAttribute; +using WebExpress.WebCore.WebPage; + +namespace WebExpress.WebCore.Test.WWW.Blog.Post +{ + /// + /// A dummy class for testing purposes. + /// + [Title("webindex:add.label")] + public sealed class Add : Page + { + /// + /// Initialization of the page. Here, for example, managed resources can be loaded. + /// + /// The context of the page. + private Add(IPageContext pageContext) + { + // test the injection + if (pageContext == null) + { + throw new ArgumentNullException(nameof(pageContext), "Parameter cannot be null or empty."); + } + } + + /// + /// Processing of the page. + /// + /// The context for rendering the page. + /// The visual tree to be rendered. + public override void Process(IRenderContext renderContext, TestVisualTree visualTree) + { + // test the context + if (renderContext == null) + { + throw new ArgumentNullException(nameof(renderContext), "Parameter cannot be null or empty."); + } + + // test the visualTree + if (visualTree == null) + { + throw new ArgumentNullException(nameof(visualTree), "Parameter cannot be null or empty."); + } + } + + /// + /// Release of unmanaged resources reserved during use. + /// + public override void Dispose() + { + } + } +} diff --git a/src/WebExpress.WebCore.Test/WWW/Blog/Post/Index.cs b/src/WebExpress.WebCore.Test/WWW/Blog/Post/Index.cs new file mode 100644 index 0000000..f9f9098 --- /dev/null +++ b/src/WebExpress.WebCore.Test/WWW/Blog/Post/Index.cs @@ -0,0 +1,52 @@ +using WebExpress.WebCore.WebAttribute; +using WebExpress.WebCore.WebPage; + +namespace WebExpress.WebCore.Test.WWW.Blog.Post +{ + /// + /// A dummy class for testing purposes. + /// + [Title("webindex:index.label")] + public sealed class Index : Page + { + /// + /// Initialization of the page. Here, for example, managed resources can be loaded. + /// + /// The context of the page. + private Index(IPageContext pageContext) + { + // test the injection + if (pageContext == null) + { + throw new ArgumentNullException(nameof(pageContext), "Parameter cannot be null or empty."); + } + } + + /// + /// Processing of the page. + /// + /// The context for rendering the page. + /// The visual tree to be rendered. + public override void Process(IRenderContext renderContext, TestVisualTree visualTree) + { + // test the context + if (renderContext == null) + { + throw new ArgumentNullException(nameof(renderContext), "Parameter cannot be null or empty."); + } + + // test the visualTree + if (visualTree == null) + { + throw new ArgumentNullException(nameof(visualTree), "Parameter cannot be null or empty."); + } + } + + /// + /// Release of unmanaged resources reserved during use. + /// + public override void Dispose() + { + } + } +} diff --git a/src/WebExpress.WebCore.Test/WWW/Blog/Post/PostId/Edit.cs b/src/WebExpress.WebCore.Test/WWW/Blog/Post/PostId/Edit.cs new file mode 100644 index 0000000..67384d7 --- /dev/null +++ b/src/WebExpress.WebCore.Test/WWW/Blog/Post/PostId/Edit.cs @@ -0,0 +1,58 @@ +using WebExpress.WebCore.WebAttribute; +using WebExpress.WebCore.WebPage; + +namespace WebExpress.WebCore.Test.WWW.Blog.Post.PostId +{ + /// + /// A dummy class for testing purposes. + /// + [Title("webindex:edit.label")] + public sealed class Edit : IPage + { + /// + /// Returns or sets the title of the page. + /// + public string Title { get; set; } + + /// + /// Returns or sets the page context. + /// + public IPageContext PageContext { get; private set; } + + /// + /// Initialization of the page. Here, for example, managed resources can be loaded. + /// + /// The context of the page. + public Edit(IPageContext pageContext) + { + PageContext = pageContext; + + // test the injection + if (pageContext == null) + { + throw new ArgumentNullException(nameof(pageContext), "Parameter cannot be null or empty."); + } + } + + /// + /// Processing of the page. + /// + /// The context for rendering the page. + /// The visual tree to be rendered. + public void Process(IRenderContext renderContext, VisualTree visualTree) + { + // test the context + if (renderContext == null) + { + throw new ArgumentNullException(nameof(renderContext), "Parameter cannot be null or empty."); + } + } + + /// + /// Release of unmanaged resources reserved during use. + /// + public void Dispose() + { + } + } +} diff --git a/src/WebExpress.WebCore.Test/WWW/Blog/Post/PostId/Index.cs b/src/WebExpress.WebCore.Test/WWW/Blog/Post/PostId/Index.cs new file mode 100644 index 0000000..130a23d --- /dev/null +++ b/src/WebExpress.WebCore.Test/WWW/Blog/Post/PostId/Index.cs @@ -0,0 +1,59 @@ +using WebExpress.WebCore.WebAttribute; +using WebExpress.WebCore.WebPage; + +namespace WebExpress.WebCore.Test.WWW.Blog.Post.PostId +{ + /// + /// A dummy class for testing purposes. + /// + [Title("webindex:index.label")] + [SegmentGuid("segment")] + public sealed class Index : IPage + { + /// + /// Returns or sets the title of the page. + /// + public string Title { get; set; } + + /// + /// Returns or sets the page context. + /// + public IPageContext PageContext { get; private set; } + + /// + /// Initialization of the page. Here, for example, managed resources can be loaded. + /// + /// The context of the page. + public Index(IPageContext pageContext) + { + PageContext = pageContext; + + // test the injection + if (pageContext == null) + { + throw new ArgumentNullException(nameof(pageContext), "Parameter cannot be null or empty."); + } + } + + /// + /// Processing of the page. + /// + /// The context for rendering the page. + /// The visual tree to be rendered. + public void Process(IRenderContext renderContext, VisualTree visualTree) + { + // test the context + if (renderContext == null) + { + throw new ArgumentNullException(nameof(renderContext), "Parameter cannot be null or empty."); + } + } + + /// + /// Release of unmanaged resources reserved during use. + /// + public void Dispose() + { + } + } +} diff --git a/src/WebExpress.WebCore.Test/WWW/Blog/Post/PostId/SegmentInfo.cs b/src/WebExpress.WebCore.Test/WWW/Blog/Post/PostId/SegmentInfo.cs new file mode 100644 index 0000000..85915e9 --- /dev/null +++ b/src/WebExpress.WebCore.Test/WWW/Blog/Post/PostId/SegmentInfo.cs @@ -0,0 +1,8 @@ +namespace WebExpress.WebCore.Test.WWW.Blog.Post.PostId +{ + //[Name("webindex:segment.label")] + //[SegmentGuid("segment")] + //public sealed class SegmentInfo + //{ + //} +} diff --git a/src/WebExpress.WebCore.Test/WWW/Contact.cs b/src/WebExpress.WebCore.Test/WWW/Contact.cs new file mode 100644 index 0000000..3697d8e --- /dev/null +++ b/src/WebExpress.WebCore.Test/WWW/Contact.cs @@ -0,0 +1,52 @@ +using WebExpress.WebCore.WebAttribute; +using WebExpress.WebCore.WebPage; + +namespace WebExpress.WebCore.Test.WWW +{ + /// + /// A dummy class for testing purposes. + /// + [Title("webindex:contact.label")] + public sealed class Contact : Page + { + /// + /// Initialization of the page. Here, for example, managed resources can be loaded. + /// + /// The context of the page. + private Contact(IPageContext pageContext) + { + // test the injection + if (pageContext == null) + { + throw new ArgumentNullException(nameof(pageContext), "Parameter cannot be null or empty."); + } + } + + /// + /// Processing of the page. + /// + /// The context for rendering the page. + /// The visual tree to be rendered. + public override void Process(IRenderContext renderContext, TestVisualTree visualTree) + { + // test the context + if (renderContext == null) + { + throw new ArgumentNullException(nameof(renderContext), "Parameter cannot be null or empty."); + } + + // test the visualTree + if (visualTree == null) + { + throw new ArgumentNullException(nameof(visualTree), "Parameter cannot be null or empty."); + } + } + + /// + /// Release of unmanaged resources reserved during use. + /// + public override void Dispose() + { + } + } +} diff --git a/src/WebExpress.WebCore.Test/WWW/Index.cs b/src/WebExpress.WebCore.Test/WWW/Index.cs new file mode 100644 index 0000000..cfc2e40 --- /dev/null +++ b/src/WebExpress.WebCore.Test/WWW/Index.cs @@ -0,0 +1,58 @@ +using WebExpress.WebCore.WebAttribute; +using WebExpress.WebCore.WebPage; + +namespace WebExpress.WebCore.Test.WWW +{ + /// + /// A dummy class for testing purposes. + /// + [Title("webindex:home.label")] + public sealed class Index : IPage + { + /// + /// Returns or sets the title of the page. + /// + public string Title { get; set; } + + /// + /// Returns or sets the page context. + /// + public IPageContext PageContext { get; private set; } + + /// + /// Initialization of the page. Here, for example, managed resources can be loaded. + /// + /// The context of the page. + public Index(IPageContext pageContext) + { + PageContext = pageContext; + + // test the injection + if (pageContext == null) + { + throw new ArgumentNullException(nameof(pageContext), "Parameter cannot be null or empty."); + } + } + + /// + /// Processing of the page. + /// + /// The context for rendering the page. + /// The visual tree to be rendered. + public void Process(IRenderContext renderContext, VisualTree visualTree) + { + // test the context + if (renderContext == null) + { + throw new ArgumentNullException(nameof(renderContext), "Parameter cannot be null or empty."); + } + } + + /// + /// Release of unmanaged resources reserved during use. + /// + public void Dispose() + { + } + } +} diff --git a/src/WebExpress.WebCore.Test/WWW/Products/Details/Index.cs b/src/WebExpress.WebCore.Test/WWW/Products/Details/Index.cs new file mode 100644 index 0000000..edba4ad --- /dev/null +++ b/src/WebExpress.WebCore.Test/WWW/Products/Details/Index.cs @@ -0,0 +1,59 @@ +using WebExpress.WebCore.WebAttribute; +using WebExpress.WebCore.WebPage; + +namespace WebExpress.WebCore.Test.WWW.Products.Details +{ + /// + /// A dummy class for testing purposes. + /// + [Title("webindex:index.label")] + [SegmentGuid("")] + public sealed class Index : IPage + { + /// + /// Returns or sets the title of the page. + /// + public string Title { get; set; } + + /// + /// Returns or sets the page context. + /// + public IPageContext PageContext { get; private set; } + + /// + /// Initialization of the page. Here, for example, managed resources can be loaded. + /// + /// The context of the page. + public Index(IPageContext pageContext) + { + PageContext = pageContext; + + // test the injection + if (pageContext == null) + { + throw new ArgumentNullException(nameof(pageContext), "Parameter cannot be null or empty."); + } + } + + /// + /// Processing of the page. + /// + /// The context for rendering the page. + /// The visual tree to be rendered. + public void Process(IRenderContext renderContext, VisualTree visualTree) + { + // test the context + if (renderContext == null) + { + throw new ArgumentNullException(nameof(renderContext), "Parameter cannot be null or empty."); + } + } + + /// + /// Release of unmanaged resources reserved during use. + /// + public void Dispose() + { + } + } +} diff --git a/src/WebExpress.WebCore.Test/WWW/Products/Index.cs b/src/WebExpress.WebCore.Test/WWW/Products/Index.cs new file mode 100644 index 0000000..4349317 --- /dev/null +++ b/src/WebExpress.WebCore.Test/WWW/Products/Index.cs @@ -0,0 +1,58 @@ +using WebExpress.WebCore.WebAttribute; +using WebExpress.WebCore.WebPage; + +namespace WebExpress.WebCore.Test.WWW.Products +{ + /// + /// A dummy class for testing purposes. + /// + [Title("webindex:index.label")] + public sealed class Index : IPage + { + /// + /// Returns or sets the title of the page. + /// + public string Title { get; set; } + + /// + /// Returns or sets the page context. + /// + public IPageContext PageContext { get; private set; } + + /// + /// Initialization of the page. Here, for example, managed resources can be loaded. + /// + /// The context of the page. + public Index(IPageContext pageContext) + { + PageContext = pageContext; + + // test the injection + if (pageContext == null) + { + throw new ArgumentNullException(nameof(pageContext), "Parameter cannot be null or empty."); + } + } + + /// + /// Processing of the page. + /// + /// The context for rendering the page. + /// The visual tree to be rendered. + public void Process(IRenderContext renderContext, VisualTree visualTree) + { + // test the context + if (renderContext == null) + { + throw new ArgumentNullException(nameof(renderContext), "Parameter cannot be null or empty."); + } + } + + /// + /// Release of unmanaged resources reserved during use. + /// + public void Dispose() + { + } + } +} diff --git a/src/WebExpress.WebCore.Test/WWW/Products/List.cs b/src/WebExpress.WebCore.Test/WWW/Products/List.cs new file mode 100644 index 0000000..089f34e --- /dev/null +++ b/src/WebExpress.WebCore.Test/WWW/Products/List.cs @@ -0,0 +1,58 @@ +using WebExpress.WebCore.WebAttribute; +using WebExpress.WebCore.WebPage; + +namespace WebExpress.WebCore.Test.WWW.Products +{ + /// + /// A dummy class for testing purposes. + /// + [Title("webindex:list.label")] + public sealed class List : IPage + { + /// + /// Returns or sets the title of the page. + /// + public string Title { get; set; } + + /// + /// Returns or sets the page context. + /// + public IPageContext PageContext { get; private set; } + + /// + /// Initialization of the page. Here, for example, managed resources can be loaded. + /// + /// The context of the page. + public List(IPageContext pageContext) + { + PageContext = pageContext; + + // test the injection + if (pageContext == null) + { + throw new ArgumentNullException(nameof(pageContext), "Parameter cannot be null or empty."); + } + } + + /// + /// Processing of the page. + /// + /// The context for rendering the page. + /// The visual tree to be rendered. + public void Process(IRenderContext renderContext, VisualTree visualTree) + { + // test the context + if (renderContext == null) + { + throw new ArgumentNullException(nameof(renderContext), "Parameter cannot be null or empty."); + } + } + + /// + /// Release of unmanaged resources reserved during use. + /// + public void Dispose() + { + } + } +} diff --git a/src/WebExpress.WebCore.Test/WWW/Resources/TestResourceA.cs b/src/WebExpress.WebCore.Test/WWW/Resources/TestResourceA.cs new file mode 100644 index 0000000..4f65bf6 --- /dev/null +++ b/src/WebExpress.WebCore.Test/WWW/Resources/TestResourceA.cs @@ -0,0 +1,47 @@ +using WebExpress.WebCore.WebMessage; +using WebExpress.WebCore.WebResource; + +namespace WebExpress.WebCore.Test.WWW.Resources +{ + /// + /// A dummy class for testing purposes. + /// + public sealed class TestResourceA : IResource + { + /// + /// Initialization of the resource. Here, for example, managed resources can be loaded. + /// + /// The context of the resource. + public TestResourceA(IResourceContext resourceContext) + { + // test the injection + if (resourceContext == null) + { + throw new ArgumentNullException(nameof(resourceContext), "Parameter cannot be null or empty."); + } + } + + /// + /// Processes the request. + /// + /// The request. + /// The processed response. + public Response Process(Request request) + { + // test the request + if (request == null) + { + throw new ArgumentNullException(nameof(request), "Parameter cannot be null or empty."); + } + + return null; + } + + /// + /// Release of unmanaged resources reserved during use. + /// + public void Dispose() + { + } + } +} diff --git a/src/WebExpress.WebCore.Test/WWW/Resources/TestResourceB.cs b/src/WebExpress.WebCore.Test/WWW/Resources/TestResourceB.cs new file mode 100644 index 0000000..ab0c2b4 --- /dev/null +++ b/src/WebExpress.WebCore.Test/WWW/Resources/TestResourceB.cs @@ -0,0 +1,41 @@ +using WebExpress.WebCore.WebMessage; +using WebExpress.WebCore.WebResource; + +namespace WebExpress.WebCore.Test.WWW.Resources +{ + /// + /// A dummy class for testing purposes. + /// + public sealed class TestResourceB : IResource + { + /// + /// Initialization of the resource. Here, for example, managed resources can be loaded. + /// + public TestResourceB() + { + } + + /// + /// Processes the request. + /// + /// The request. + /// The processed response. + public Response Process(Request request) + { + // test the request + if (request == null) + { + throw new ArgumentNullException(nameof(request), "Parameter cannot be null or empty."); + } + + return null; + } + + /// + /// Release of unmanaged resources reserved during use. + /// + public void Dispose() + { + } + } +} diff --git a/src/WebExpress.WebCore.Test/WWW/Resources/TestResourceC.cs b/src/WebExpress.WebCore.Test/WWW/Resources/TestResourceC.cs new file mode 100644 index 0000000..538e866 --- /dev/null +++ b/src/WebExpress.WebCore.Test/WWW/Resources/TestResourceC.cs @@ -0,0 +1,54 @@ +using WebExpress.WebCore.WebMessage; +using WebExpress.WebCore.WebResource; + +namespace WebExpress.WebCore.Test.WWW.Resources +{ + /// + /// A dummy class for testing purposes. + /// + public sealed class TestResourceC : IResource + { + /// + /// Initialization of the resource. Here, for example, managed resources can be loaded. + /// + /// The resource manager. + /// The context of the resource. + public TestResourceC(IResourceManager resourceManager, IResourceContext resourceContext) + { + // test the injection + if (resourceManager == null) + { + throw new ArgumentNullException(nameof(resourceManager), "Parameter cannot be null or empty."); + } + + // test the injection + if (resourceContext == null) + { + throw new ArgumentNullException(nameof(resourceContext), "Parameter cannot be null or empty."); + } + } + + /// + /// Processes the request. + /// + /// The request. + /// The processed response. + public Response Process(Request request) + { + // test the request + if (request == null) + { + throw new ArgumentNullException(nameof(request), "Parameter cannot be null or empty."); + } + + return null; + } + + /// + /// Release of unmanaged resources reserved during use. + /// + public void Dispose() + { + } + } +} diff --git a/src/WebExpress.WebCore.Test/WWW/Resources/TestResourceD.cs b/src/WebExpress.WebCore.Test/WWW/Resources/TestResourceD.cs new file mode 100644 index 0000000..7cc397c --- /dev/null +++ b/src/WebExpress.WebCore.Test/WWW/Resources/TestResourceD.cs @@ -0,0 +1,54 @@ +using WebExpress.WebCore.WebMessage; +using WebExpress.WebCore.WebResource; + +namespace WebExpress.WebCore.Test.WWW.Resources +{ + /// + /// A dummy class for testing purposes. + /// + public sealed class TestResourceD : IResource + { + /// + /// Initialization of the resource. Here, for example, managed resources can be loaded. + /// + /// The context of the resource. + /// The resource manager. + public TestResourceD(IResourceContext resourceContext, IResourceManager resourceManager) + { + // test the injection + if (resourceManager == null) + { + throw new ArgumentNullException(nameof(resourceManager), "Parameter cannot be null or empty."); + } + + // test the injection + if (resourceContext == null) + { + throw new ArgumentNullException(nameof(resourceContext), "Parameter cannot be null or empty."); + } + } + + /// + /// Processes the request. + /// + /// The request. + /// The processed response. + public Response Process(Request request) + { + // test the request + if (request == null) + { + throw new ArgumentNullException(nameof(request), "Parameter cannot be null or empty."); + } + + return null; + } + + /// + /// Release of unmanaged resources reserved during use. + /// + public void Dispose() + { + } + } +} diff --git a/src/WebExpress.WebCore.Test/WWW/Settings/TestSettingCategoryA.cs b/src/WebExpress.WebCore.Test/WWW/Settings/TestSettingCategoryA.cs new file mode 100644 index 0000000..79db23e --- /dev/null +++ b/src/WebExpress.WebCore.Test/WWW/Settings/TestSettingCategoryA.cs @@ -0,0 +1,16 @@ +using WebExpress.WebCore.WebAttribute; +using WebExpress.WebCore.WebSettingPage; + +namespace WebExpress.WebCore.Test.WWW.Settings +{ + /// + /// A dummy setting category for testing purposes. + /// + [WebIcon] + [Name("SettingCategory A")] + [Description("Description of category a.")] + [SettingSection(SettingSection.Preferences)] + public sealed class TestSettingCategoryA : ISettingCategory + { + } +} diff --git a/src/WebExpress.WebCore.Test/WWW/Settings/TestSettingCategoryB.cs b/src/WebExpress.WebCore.Test/WWW/Settings/TestSettingCategoryB.cs new file mode 100644 index 0000000..bd6e566 --- /dev/null +++ b/src/WebExpress.WebCore.Test/WWW/Settings/TestSettingCategoryB.cs @@ -0,0 +1,16 @@ +using WebExpress.WebCore.WebAttribute; +using WebExpress.WebCore.WebSettingPage; + +namespace WebExpress.WebCore.Test.WWW.Settings +{ + /// + /// A dummy setting category for testing purposes. + /// + [WebIcon] + [Name("SettingCategory B")] + [Description("Description of category b.")] + [SettingSection(SettingSection.Primary)] + public sealed class TestSettingCategoryB : ISettingCategory + { + } +} diff --git a/src/WebExpress.WebCore.Test/WWW/Settings/TestSettingCategoryC.cs b/src/WebExpress.WebCore.Test/WWW/Settings/TestSettingCategoryC.cs new file mode 100644 index 0000000..725c66c --- /dev/null +++ b/src/WebExpress.WebCore.Test/WWW/Settings/TestSettingCategoryC.cs @@ -0,0 +1,15 @@ +using WebExpress.WebCore.WebAttribute; +using WebExpress.WebCore.WebSettingPage; + +namespace WebExpress.WebCore.Test.WWW.Settings +{ + /// + /// A dummy setting category for testing purposes. + /// + [Name("SettingCategory C")] + [Description("Description of category c.")] + [SettingSection(SettingSection.Secondary)] + public sealed class TestSettingCategoryC : ISettingCategory + { + } +} diff --git a/src/WebExpress.WebCore.Test/WWW/Settings/TestSettingGroupA.cs b/src/WebExpress.WebCore.Test/WWW/Settings/TestSettingGroupA.cs new file mode 100644 index 0000000..81a99df --- /dev/null +++ b/src/WebExpress.WebCore.Test/WWW/Settings/TestSettingGroupA.cs @@ -0,0 +1,17 @@ +using WebExpress.WebCore.WebAttribute; +using WebExpress.WebCore.WebSettingPage; + +namespace WebExpress.WebCore.Test.WWW.Settings +{ + /// + /// A dummy setting group for testing purposes. + /// + [WebIcon] + [Name("SettingGroup A")] + [Description("Description of group a.")] + [SettingCategory()] + [SettingSection(SettingSection.Preferences)] + public sealed class TestSettingGroupA : ISettingGroup + { + } +} diff --git a/src/WebExpress.WebCore.Test/WWW/Settings/TestSettingGroupB.cs b/src/WebExpress.WebCore.Test/WWW/Settings/TestSettingGroupB.cs new file mode 100644 index 0000000..05f815a --- /dev/null +++ b/src/WebExpress.WebCore.Test/WWW/Settings/TestSettingGroupB.cs @@ -0,0 +1,17 @@ +using WebExpress.WebCore.WebAttribute; +using WebExpress.WebCore.WebSettingPage; + +namespace WebExpress.WebCore.Test.WWW.Settings +{ + /// + /// A dummy setting group for testing purposes. + /// + [WebIcon()] + [Name("SettingGroup B")] + [Description("Description of group b.")] + [SettingCategory()] + [SettingSection(SettingSection.Primary)] + public sealed class TestSettingGroupB : ISettingGroup + { + } +} diff --git a/src/WebExpress.WebCore.Test/WWW/Settings/TestSettingGroupC.cs b/src/WebExpress.WebCore.Test/WWW/Settings/TestSettingGroupC.cs new file mode 100644 index 0000000..8bb3bf2 --- /dev/null +++ b/src/WebExpress.WebCore.Test/WWW/Settings/TestSettingGroupC.cs @@ -0,0 +1,15 @@ +using WebExpress.WebCore.WebAttribute; +using WebExpress.WebCore.WebSettingPage; + +namespace WebExpress.WebCore.Test.WWW.Settings +{ + /// + /// A dummy setting group for testing purposes. + /// + [Name("SettingGroup C")] + [Description("Description of group c.")] + [SettingSection(SettingSection.Secondary)] + public sealed class TestSettingGroupC : ISettingGroup + { + } +} diff --git a/src/WebExpress.WebCore.Test/WWW/Settings/TestSettingPageA.cs b/src/WebExpress.WebCore.Test/WWW/Settings/TestSettingPageA.cs new file mode 100644 index 0000000..81340d0 --- /dev/null +++ b/src/WebExpress.WebCore.Test/WWW/Settings/TestSettingPageA.cs @@ -0,0 +1,49 @@ +using WebExpress.WebCore.WebAttribute; +using WebExpress.WebCore.WebPage; +using WebExpress.WebCore.WebSettingPage; + +namespace WebExpress.WebCore.Test.WWW.Settings +{ + /// + /// A dummy class for testing purposes. + /// + [WebIcon] + [Title("webindex:settingpagea.label")] + [SettingGroup()] + public sealed class TestSettingPageA : ISettingPage + { + /// + /// Returns or sets the setting page context. + /// + public ISettingPageContext PageContext { get; private set; } + + /// + /// Initialization of the page. Here, for example, managed resources can be loaded. + /// + /// The context of the setting page. + public TestSettingPageA(ISettingPageContext pageContext) + { + PageContext = pageContext; + + // test the injection + if (pageContext == null) + { + throw new ArgumentNullException(nameof(pageContext), "Parameter cannot be null or empty."); + } + } + + /// + /// Processing of the page. + /// + /// The context for rendering the setting page. + /// The visual tree to be rendered. + public void Process(IRenderContext renderContext, VisualTree visualTree) + { + // test the context + if (renderContext == null) + { + throw new ArgumentNullException(nameof(renderContext), "Parameter cannot be null or empty."); + } + } + } +} diff --git a/src/WebExpress.WebCore.Test/WWW/Settings/TestSettingPageB.cs b/src/WebExpress.WebCore.Test/WWW/Settings/TestSettingPageB.cs new file mode 100644 index 0000000..4530e2a --- /dev/null +++ b/src/WebExpress.WebCore.Test/WWW/Settings/TestSettingPageB.cs @@ -0,0 +1,49 @@ +using WebExpress.WebCore.WebAttribute; +using WebExpress.WebCore.WebPage; +using WebExpress.WebCore.WebSettingPage; + +namespace WebExpress.WebCore.Test.WWW.Settings +{ + /// + /// A dummy class for testing purposes. + /// + [WebIcon] + [Title("webindex:settingpageb.label")] + [SettingGroup] + public sealed class TestSettingPageB : ISettingPage + { + /// + /// Returns or sets the setting page context. + /// + public ISettingPageContext PageContext { get; private set; } + + /// + /// Initialization of the page. Here, for example, managed resources can be loaded. + /// + /// The context of the setting page. + public TestSettingPageB(ISettingPageContext pageContext) + { + PageContext = pageContext; + + // test the injection + if (pageContext == null) + { + throw new ArgumentNullException(nameof(pageContext), "Parameter cannot be null or empty."); + } + } + + /// + /// Processing of the page. + /// + /// The context for rendering the setting page. + /// The visual tree to be rendered. + public void Process(IRenderContext renderContext, VisualTree visualTree) + { + // test the context + if (renderContext == null) + { + throw new ArgumentNullException(nameof(renderContext), "Parameter cannot be null or empty."); + } + } + } +} diff --git a/src/WebExpress.WebCore.Test/WWW/Settings/TestSettingPageC.cs b/src/WebExpress.WebCore.Test/WWW/Settings/TestSettingPageC.cs new file mode 100644 index 0000000..3f29a23 --- /dev/null +++ b/src/WebExpress.WebCore.Test/WWW/Settings/TestSettingPageC.cs @@ -0,0 +1,47 @@ +using WebExpress.WebCore.WebAttribute; +using WebExpress.WebCore.WebPage; +using WebExpress.WebCore.WebSettingPage; + +namespace WebExpress.WebCore.Test.WWW.Settings +{ + /// + /// A dummy class for testing purposes. + /// + [Title("webindex:settingpageb.label")] + public sealed class TestSettingPageC : ISettingPage + { + /// + /// Returns or sets the setting page context. + /// + public ISettingPageContext PageContext { get; private set; } + + /// + /// Initialization of the page. Here, for example, managed resources can be loaded. + /// + /// The context of the setting page. + public TestSettingPageC(ISettingPageContext pageContext) + { + PageContext = pageContext; + + // test the injection + if (pageContext == null) + { + throw new ArgumentNullException(nameof(pageContext), "Parameter cannot be null or empty."); + } + } + + /// + /// Processing of the page. + /// + /// The context for rendering the setting page. + /// The visual tree to be rendered. + public void Process(IRenderContext renderContext, VisualTree visualTree) + { + // test the context + if (renderContext == null) + { + throw new ArgumentNullException(nameof(renderContext), "Parameter cannot be null or empty."); + } + } + } +} diff --git a/src/WebExpress.WebCore.Test/WebExpress.WebCore.Test.csproj b/src/WebExpress.WebCore.Test/WebExpress.WebCore.Test.csproj index 0e8542a..aa17a51 100644 --- a/src/WebExpress.WebCore.Test/WebExpress.WebCore.Test.csproj +++ b/src/WebExpress.WebCore.Test/WebExpress.WebCore.Test.csproj @@ -1,18 +1,66 @@  - net8.0 + net9.0 enable - enable + disable false true - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/src/WebExpress.WebCore/ArgumentParser.cs b/src/WebExpress.WebCore/ArgumentParser.cs index 37f4412..8b473f7 100644 --- a/src/WebExpress.WebCore/ArgumentParser.cs +++ b/src/WebExpress.WebCore/ArgumentParser.cs @@ -26,21 +26,18 @@ public static ArgumentParser Current { get { - if (m_this == null) - { - m_this = new ArgumentParser(); - } + m_this ??= new ArgumentParser(); return m_this; } } /// - /// Constructor + /// Initializes a new instance of the class. /// public ArgumentParser() { - Commands = new List(); + Commands = []; } /// @@ -72,7 +69,7 @@ public ArguemtParserResult Parse(string[] args) if (s.StartsWith("--") == true) { } - else if (s.StartsWith("-") == true) + else if (s.StartsWith('-') == true) { if (!string.IsNullOrEmpty(key)) { diff --git a/src/WebExpress.WebCore/Config/EndpointConfig.cs b/src/WebExpress.WebCore/Config/EndpointConfig.cs index 4f89d9f..d3421b7 100644 --- a/src/WebExpress.WebCore/Config/EndpointConfig.cs +++ b/src/WebExpress.WebCore/Config/EndpointConfig.cs @@ -27,7 +27,7 @@ public sealed class EndpointConfig public string Password { get; set; } /// - /// Constructor + /// Initializes a new instance of the class. /// public EndpointConfig() { diff --git a/src/WebExpress.WebCore/Config/HttpServerConfig.cs b/src/WebExpress.WebCore/Config/HttpServerConfig.cs index adfa1da..7a80762 100644 --- a/src/WebExpress.WebCore/Config/HttpServerConfig.cs +++ b/src/WebExpress.WebCore/Config/HttpServerConfig.cs @@ -23,10 +23,10 @@ public sealed class HttpServerConfig public List Endpoints { get; set; } /// - /// The uri of the web server. + /// The route of the web server. /// - [XmlElement("uri")] - public string Uri { get; set; } + [XmlElement("route")] + public string Route { get; set; } /// /// The limitations. @@ -71,7 +71,7 @@ public sealed class HttpServerConfig public SettingLogItem Log { get; set; } /// - /// Constructor + /// Initializes a new instance of the class. /// public HttpServerConfig() { diff --git a/src/WebExpress.WebCore/Config/LimitConfig.cs b/src/WebExpress.WebCore/Config/LimitConfig.cs index e3e41a0..284ae85 100644 --- a/src/WebExpress.WebCore/Config/LimitConfig.cs +++ b/src/WebExpress.WebCore/Config/LimitConfig.cs @@ -21,7 +21,7 @@ public sealed class LimitConfig public long UploadLimit { get; set; } /// - /// Constructor + /// Initializes a new instance of the class. /// public LimitConfig() { diff --git a/src/WebExpress.WebCore/Config/PluginConfig.cs b/src/WebExpress.WebCore/Config/PluginConfig.cs index 8c1ebe2..2ecd89a 100644 --- a/src/WebExpress.WebCore/Config/PluginConfig.cs +++ b/src/WebExpress.WebCore/Config/PluginConfig.cs @@ -21,7 +21,7 @@ public sealed class PluginConfig public string ContextPath { get; set; } /// - /// Constructor + /// Initializes a new instance of the class. /// public PluginConfig() { diff --git a/src/WebExpress.WebCore/HttpServer.cs b/src/WebExpress.WebCore/HttpServer.cs index 1674dfa..dfb0a95 100644 --- a/src/WebExpress.WebCore/HttpServer.cs +++ b/src/WebExpress.WebCore/HttpServer.cs @@ -17,13 +17,10 @@ using System.Threading.Tasks; using WebExpress.WebCore.Config; using WebExpress.WebCore.Internationalization; -using WebExpress.WebCore.WebApplication; -using WebExpress.WebCore.WebComponent; using WebExpress.WebCore.WebHtml; +using WebExpress.WebCore.WebLog; using WebExpress.WebCore.WebMessage; -using WebExpress.WebCore.WebModule; using WebExpress.WebCore.WebPage; -using WebExpress.WebCore.WebResource; using WebExpress.WebCore.WebSitemap; using WebExpress.WebCore.WebUri; @@ -32,7 +29,7 @@ namespace WebExpress.WebCore /// /// The web server for processing http requests (see RFC 2616). The web server uses Kestrel internally. /// - public class HttpServer : IHost, II18N, IHttpApplication + public class HttpServer : IHost, IHttpApplication { /// /// Event is triggered after the web server is started. @@ -75,14 +72,14 @@ public class HttpServer : IHost, II18N, IHttpApplication public long RequestNumber { get; private set; } /// - /// Constructor + /// Initializes a new instance of the class. /// /// Der Serverkontext. public HttpServer(HttpServerContext context) { HttpServerContext = new HttpServerContext ( - context.Uri, + context.Route, context.Endpoints, context.PackagePath, context.AssetPath, @@ -95,8 +92,6 @@ public HttpServer(HttpServerContext context) ); Culture = HttpServerContext.Culture; - - ComponentManager.Initialization(HttpServerContext); } /// @@ -106,12 +101,12 @@ public void Start() { if (HttpServerContext != null && HttpServerContext.Log != null) { - HttpServerContext.Log.Info(message: this.I18N("webexpress:httpserver.run")); + HttpServerContext.Log.Info(message: I18N.Translate("webexpress.webcore:httpserver.run")); } if (!HttpListener.IsSupported) { - HttpServerContext.Log.Error(message: this.I18N("webexpress:httpserver.notsupported")); + HttpServerContext.Log.Error(message: I18N.Translate("webexpress.webcore:httpserver.notsupported")); } var logger = new LogFactory(); @@ -122,7 +117,7 @@ public void Start() serviceCollection.AddMemoryCache(); serviceCollection.AddLogging(x => { - x.SetMinimumLevel(LogLevel.Trace); + x.SetMinimumLevel(Microsoft.Extensions.Logging.LogLevel.Trace); x.AddProvider(logger); }); serviceCollection.AddHttpLogging(x => @@ -151,7 +146,7 @@ public void Start() Kestrel.StartAsync(this, ServerToken); - HttpServerContext.Log.Info(message: this.I18N("webexpress:httpserver.start"), args: new object[] { ExecutionTime.ToShortDateString(), ExecutionTime.ToLongTimeString() }); + HttpServerContext.Log.Info(message: I18N.Translate("webexpress.webcore:httpserver.start"), args: [ExecutionTime.ToShortDateString(), ExecutionTime.ToLongTimeString()]); Started?.Invoke(this, new EventArgs()); } @@ -171,10 +166,10 @@ private void AddEndpoint(OptionsWrapper serverOptions, End var port = uri.Port; var host = asterisk ? Dns.GetHostEntry(Dns.GetHostName()) : Dns.GetHostEntry(uri.Host); var addressList = host.AddressList - .Union(asterisk ? Dns.GetHostEntry("localhost").AddressList : Array.Empty()) + .Union(asterisk ? Dns.GetHostEntry("localhost").AddressList : []) .Where(x => x.AddressFamily == AddressFamily.InterNetwork || x.AddressFamily == AddressFamily.InterNetworkV6); - HttpServerContext.Log.Info(message: this.I18N("webexpress:httpserver.endpoint"), args: endPoint.Uri); + HttpServerContext.Log.Info(message: I18N.Translate("webexpress.webcore:httpserver.endpoint"), args: endPoint.Uri); foreach (var ipAddress in addressList) { @@ -190,9 +185,8 @@ private void AddEndpoint(OptionsWrapper serverOptions, End } catch (Exception ex) { - HttpServerContext.Log.Error(message: this.I18N("webexpress:httpserver.listen.exeption"), args: endPoint); + HttpServerContext.Log.Error(message: I18N.Translate("webexpress.webcore:httpserver.listen.exeption"), args: endPoint); HttpServerContext.Log.Exception(ex); - } } @@ -205,26 +199,26 @@ private void AddEndpoint(OptionsWrapper serverOptions, IPE { serverOptions.Value.Listen(endPoint); - HttpServerContext.Log.Info(message: this.I18N("webexpress:httpserver.listen"), args: endPoint.ToString()); + HttpServerContext.Log.Info(message: I18N.Translate("webexpress.webcore:httpserver.listen"), args: endPoint.ToString()); } /// - /// Adds an endpoint. + /// Adds an endpoint with HTTPS configuration. /// /// The server options. - /// The certificate. - /// The password to the certificate. /// The endpoint. + /// The path to the PFX file containing the certificate. + /// The password for the PFX file. private void AddEndpoint(OptionsWrapper serverOptions, IPEndPoint endPoint, string pfxFile, string password) { serverOptions.Value.Listen(endPoint, configure => { - var cert = new X509Certificate2(pfxFile, password); + var cert = X509CertificateLoader.LoadPkcs12FromFile(pfxFile, password, X509KeyStorageFlags.DefaultKeySet); configure.UseHttps(cert); }); - HttpServerContext.Log.Info(message: this.I18N("webexpress:httpserver.listen"), args: endPoint.ToString()); + HttpServerContext.Log.Info(message: I18N.Translate("webexpress.webcore:httpserver.listen"), args: endPoint.ToString()); } /// @@ -234,9 +228,6 @@ public void Stop() { // End running threads Kestrel.StopAsync(ServerToken); - - // Stop running - ComponentManager.ShutDown(); } /// @@ -253,17 +244,17 @@ private Response HandleClient(HttpContext context) var culture = request.Culture; var uri = request?.Uri; - HttpServerContext.Log.Debug(message: this.I18N("webexpress:httpserver.connected"), args: context.RemoteEndPoint); - HttpServerContext.Log.Info(InternationalizationManager.I18N + HttpServerContext.Log.Debug(message: I18N.Translate("webexpress.webcore:httpserver.connected"), args: context.RemoteEndPoint); + HttpServerContext.Log.Info(I18N.Translate ( - "webexpress:httpserver.request", + "webexpress.webcore:httpserver.request", context.RemoteEndPoint, ++RequestNumber, $"{request?.Method} {request?.Uri} {request?.Protocoll}" )); // search page in sitemap - var searchResult = ComponentManager.SitemapManager.SearchResource(context.Uri, new SearchContext() + var searchResult = WebEx.ComponentHub.SitemapManager.SearchResource(context.Uri, new SearchContext() { Culture = culture, HttpContext = context, @@ -272,13 +263,7 @@ private Response HandleClient(HttpContext context) if (searchResult != null) { - var resourceUri = new UriResource(request.Uri, searchResult.Uri.PathSegments); - resourceUri = new UriResource(resourceUri, resourceUri.PathSegments, request.Uri.Skip(resourceUri.PathSegments.Count())?.PathSegments); - resourceUri.ServerRoot = new UriResource(request.Uri, HttpServerContext.ContextPath.PathSegments); - resourceUri.ApplicationRoot = new UriResource(request.Uri, searchResult.ApplicationContext?.ContextPath.PathSegments); - resourceUri.ModuleRoot = new UriResource(request.Uri, searchResult.ModuleContext?.ContextPath.PathSegments); - resourceUri.ResourceRoot = new UriResource(request.Uri, searchResult.Uri.PathSegments); - + var resourceUri = new UriEndpoint(request.Uri, searchResult.Uri.PathSegments); request.Uri = resourceUri; try @@ -286,16 +271,9 @@ private Response HandleClient(HttpContext context) // execute resource request.AddParameter(searchResult.Uri.Parameters.Select(x => new Parameter(x.Key, x.Value, ParameterScope.Url))); - if (searchResult.Instance != null) + if (searchResult.EndpointContext != null) { - searchResult.Instance?.PreProcess(request); - response = searchResult.Instance?.Process(request); - response = searchResult.Instance?.PostProcess(request, response); - - if (searchResult.Instance is IPage) - { - response.Content += $""; - } + response = WebEx.ComponentHub.EndpointManager.HandleRequest(request, searchResult.EndpointContext); if (response is ResponseNotFound) { @@ -365,9 +343,9 @@ private Response HandleClient(HttpContext context) stopwatch.Stop(); - HttpServerContext.Log.Info(InternationalizationManager.I18N + HttpServerContext.Log.Info(I18N.Translate ( - "webexpress:httpserver.request.done", + "webexpress.webcore:httpserver.request.done", context?.RemoteEndPoint, RequestNumber, stopwatch.ElapsedMilliseconds, @@ -393,7 +371,6 @@ private async Task SendResponseAsync(HttpContext context, Response response) responseFeature.StatusCode = response.Status; responseFeature.ReasonPhrase = response.Reason; responseFeature.Headers.KeepAlive = "true"; - responseFeature.Headers.Add("PageID", "Test"); if (response.Header.Location != null) { @@ -415,7 +392,7 @@ private async Task SendResponseAsync(HttpContext context, Response response) responseFeature.Headers.WWWAuthenticate = "Basic realm=\"Bereich\""; } - if (response.Header.Cookies.Any()) + if (response.Header.Cookies.Count != 0) { responseFeature.Headers.SetCookie = string.Join(" ", response.Header.Cookies); } @@ -454,73 +431,28 @@ private async Task SendResponseAsync(HttpContext context, Response response) /// /// Creates a status page /// - /// The error message. + /// The error message. /// The request. /// The plugin by searching the status page or null. /// The response. - private Response CreateStatusPage(string massage, Request request, SearchResult searchResult = null) where T : Response, new() + private static Response CreateStatusPage(string message, Request request, SearchResult searchResult = null) where T : Response, new() { var response = new T() as Response; - var culture = Culture; - - try - { - culture = new CultureInfo(request?.Header?.AcceptLanguage?.FirstOrDefault()?.ToLower()); - } - catch - { - } if (searchResult != null) { - var statusPage = ComponentManager.ResponseManager.CreateStatusPage + return WebEx.ComponentHub.StatusPageManager.CreateStatusResponse ( - massage, + message, response.Status, - searchResult?.ModuleContext?.PluginContext ?? - searchResult?.ApplicationContext?.PluginContext + searchResult?.EndpointContext?.ApplicationContext, + request ); - - if (statusPage == null) - { - return response; - } - - if (statusPage is II18N i18n) - { - i18n.Culture = culture; - } - - if (statusPage is Resource resource) - { - resource.ApplicationContext = searchResult?.ApplicationContext ?? new ApplicationContext() - { - PluginContext = searchResult?.ModuleContext?.PluginContext ?? - searchResult?.ApplicationContext?.PluginContext, - ApplicationId = "webex", - ApplicationName = "WebExpress", - ContextPath = new UriResource() - }; - - resource.ModuleContext = searchResult?.ModuleContext ?? new ModuleContext() - { - ApplicationContext = resource.ApplicationContext, - PluginContext = searchResult?.ModuleContext?.PluginContext ?? - searchResult?.ApplicationContext?.PluginContext, - ModuleId = "webex", - ModuleName = "WebExpress", - ContextPath = new UriResource() - }; - - resource.Initialization(new ResourceContext(resource.ModuleContext)); - } - - return statusPage.Process(request); } - var message = $"{response.Status}" + - $"

{massage}

" + - $""; + message = $"{response.Status}" + + $"

{message}

" + + $""; response.Content = message; response.Header.ContentLength = message.Length; @@ -538,7 +470,7 @@ public HttpContext CreateContext(IFeatureCollection contextFeatures) { try { - return new HttpContext(contextFeatures, this.HttpServerContext); + return new HttpContext(contextFeatures, HttpServerContext); } catch (Exception ex) { diff --git a/src/WebExpress.WebCore/HttpServerContext.cs b/src/WebExpress.WebCore/HttpServerContext.cs index 1f94c11..76db1ee 100644 --- a/src/WebExpress.WebCore/HttpServerContext.cs +++ b/src/WebExpress.WebCore/HttpServerContext.cs @@ -2,7 +2,8 @@ using System.Globalization; using System.Reflection; using WebExpress.WebCore.Config; -using WebExpress.WebCore.WebUri; +using WebExpress.WebCore.WebEndpoint; +using WebExpress.WebCore.WebLog; namespace WebExpress.WebCore { @@ -12,9 +13,9 @@ namespace WebExpress.WebCore public class HttpServerContext : IHttpServerContext { ///

- /// Returns the uri of the web server. + /// Returns the route of the web server. /// - public string Uri { get; protected set; } + public IRoute Route { get; protected set; } /// /// Returns the endpoints to which the web server responds. @@ -49,7 +50,7 @@ public class HttpServerContext : IHttpServerContext /// /// Returns the basic context path. /// - public UriResource ContextPath { get; protected set; } + public IRoute ContextPath { get; protected set; } /// /// Returns the culture. @@ -59,7 +60,7 @@ public class HttpServerContext : IHttpServerContext /// /// Returns the log for writing status messages to the console and to a log file. /// - public Log Log { get; protected set; } + public ILog Log { get; protected set; } /// /// Returns the host. @@ -67,9 +68,9 @@ public class HttpServerContext : IHttpServerContext public IHost Host { get; protected set; } /// - /// Constructor + /// Initializes a new instance of the class. /// - /// The uri of the web server. + /// The uri of the route server. /// The endpoints to which the web server responds. /// The package home directory.chnis /// The asset home directory. @@ -81,22 +82,22 @@ public class HttpServerContext : IHttpServerContext /// The host. public HttpServerContext ( - string uri, + IRoute route, ICollection endpoints, string packageBaseFolder, string assetBaseFolder, string dataBaseFolder, string configBaseFolder, - UriResource contextPath, + IRoute contextPath, CultureInfo culture, - Log log, + ILog log, IHost host ) { var assembly = typeof(HttpServer).Assembly; Version = assembly.GetCustomAttribute()?.InformationalVersion; - Uri = uri; + Route = route; Endpoints = endpoints; PackagePath = packageBaseFolder; AssetPath = assetBaseFolder; diff --git a/src/WebExpress.WebCore/IHttpServerContext.cs b/src/WebExpress.WebCore/IHttpServerContext.cs index 26261a9..1ef850b 100644 --- a/src/WebExpress.WebCore/IHttpServerContext.cs +++ b/src/WebExpress.WebCore/IHttpServerContext.cs @@ -1,7 +1,8 @@ using System.Collections.Generic; using System.Globalization; using WebExpress.WebCore.Config; -using WebExpress.WebCore.WebUri; +using WebExpress.WebCore.WebEndpoint; +using WebExpress.WebCore.WebLog; namespace WebExpress.WebCore { @@ -11,9 +12,9 @@ namespace WebExpress.WebCore public interface IHttpServerContext { /// - /// Returns the uri of the web server. + /// Returns the route of the web server. /// - string Uri { get; } + IRoute Route { get; } /// /// Returns the endpoints to which the web server responds. @@ -48,7 +49,7 @@ public interface IHttpServerContext /// /// Returns the basic context path. /// - UriResource ContextPath { get; } + IRoute ContextPath { get; } /// /// Returns the culture. @@ -58,7 +59,7 @@ public interface IHttpServerContext /// /// Returns the log for writing status messages to the console and to a log file. /// - Log Log { get; } + ILog Log { get; } /// /// Returns the host. diff --git a/src/WebExpress.WebCore/Internationalization/I18N.cs b/src/WebExpress.WebCore/Internationalization/I18N.cs new file mode 100644 index 0000000..ec8a0f7 --- /dev/null +++ b/src/WebExpress.WebCore/Internationalization/I18N.cs @@ -0,0 +1,127 @@ +using System.Globalization; +using WebExpress.WebCore.WebMessage; +using WebExpress.WebCore.WebPage; + +namespace WebExpress.WebCore.Internationalization +{ + /// + /// Provides internationalization (i18n) functionalities. + /// + public static class I18N + { + /// + /// Translates a given key to the default language. + /// + /// The internationalization key. + /// The value of the key in the current language. + public static string Translate(string key) + { + return WebEx.ComponentHub?.InternationalizationManager?.Translate(key) ?? key; + } + + /// + /// Translates a given key to the default language. + /// + /// The internationalization key. + /// The formatting arguments. + /// The value of the key in the current language. + public static string Translate(string key, params object[] args) + { + return WebEx.ComponentHub?.InternationalizationManager?.Translate(key, args) ?? key; + } + + /// + /// Translates a given key to the specified language. + /// + /// The request with the language to use. + /// The internationalization key. + /// The value of the key in the current language. + public static string Translate(Request request, string key) + { + return WebEx.ComponentHub?.InternationalizationManager.Translate(request, key) ?? key; + } + + /// + /// Translates a given key to the specified language. + /// + /// The request with the language to use. + /// The internationalization key. + /// The formatting arguments. + /// The value of the key in the current language. + public static string Translate(Request request, string key, params object[] args) + { + return WebEx.ComponentHub?.InternationalizationManager?.Translate(request, key, args) ?? key; + } + + /// + /// Translates a given key to the specified language. + /// + /// The render context with the language to use. + /// The internationalization key. + /// The value of the key in the current language. + public static string Translate(IRenderContext renderContext, string key) + { + return WebEx.ComponentHub?.InternationalizationManager?.Translate(renderContext?.Request, key) ?? key; + } + + /// + /// Translates a given key to the specified language. + /// + /// The render context with the language to use. + /// The internationalization key. + /// The formatting arguments. + /// The value of the key in the current language. + public static string Translate(IRenderContext renderContext, string key, params object[] args) + { + return WebEx.ComponentHub?.InternationalizationManager?.Translate(renderContext?.Request, key, args) ?? key; + } + + /// + /// Translates a given key to the specified language. + /// + /// The culture with the language to use. + /// The internationalization key. + /// The value of the key in the current language. + public static string Translate(CultureInfo culture, string key) + { + return WebEx.ComponentHub?.InternationalizationManager?.Translate(culture, key) ?? key; + } + + /// + /// Translates a given key to the specified language. + /// + /// The culture with the language to use. + /// The internationalization key. + /// The formatting arguments. + /// The value of the key in the current language. + public static string Translate(CultureInfo culture, string key, params object[] args) + { + return WebEx.ComponentHub?.InternationalizationManager?.Translate(culture, key, args) ?? key; + } + + /// + /// Translates a given key to the specified language. + /// + /// The culture with the language to use. + /// The plugin id. + /// The internationalization key. + /// The value of the key in the current language. + public static string Translate(CultureInfo culture, string pluginId, string key) + { + return WebEx.ComponentHub?.InternationalizationManager?.Translate(culture, pluginId, key) ?? key; + } + + /// + /// Translates a given key to the specified language. + /// + /// The culture with the language to use. + /// The plugin id. + /// The internationalization key. + /// The formatting arguments. + /// The value of the key in the current language. + public static string Translate(CultureInfo culture, string pluginId, string key, params object[] args) + { + return WebEx.ComponentHub?.InternationalizationManager?.Translate(culture, pluginId, key, args) ?? key; + } + } +} diff --git a/src/WebExpress.WebCore/Internationalization/II18N.cs b/src/WebExpress.WebCore/Internationalization/II18N.cs deleted file mode 100644 index 1fbfa61..0000000 --- a/src/WebExpress.WebCore/Internationalization/II18N.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Globalization; - -namespace WebExpress.WebCore.Internationalization -{ - public interface II18N - { - /// - /// Returns or sets the culture. - /// - CultureInfo Culture { get; set; } - } -} diff --git a/src/WebExpress.WebCore/Internationalization/IInternationalizationManager.cs b/src/WebExpress.WebCore/Internationalization/IInternationalizationManager.cs new file mode 100644 index 0000000..cfed473 --- /dev/null +++ b/src/WebExpress.WebCore/Internationalization/IInternationalizationManager.cs @@ -0,0 +1,80 @@ +using System.Globalization; +using WebExpress.WebCore.WebComponent; +using WebExpress.WebCore.WebMessage; + +namespace WebExpress.WebCore.Internationalization +{ + /// + /// The interface of the internationalization manager. + /// + public interface IInternationalizationManager : IComponentManager + { + /// + /// Translates a given key to the default language. + /// + /// The internationalization key. + /// The value of the key in the current language. + public string Translate(string key); + + /// + /// Translates a given key to the default language. + /// + /// The internationalization key. + /// The formatting arguments. + /// The value of the key in the current language. + public string Translate(string key, params object[] args); + + /// + /// Translates a given key to the specified language. + /// + /// The request with the language to use. + /// The internationalization key. + /// The value of the key in the current language. + string Translate(Request request, string key); + + /// + /// Translates a given key to the specified language. + /// + /// The request with the language to use. + /// The internationalization key. + /// The formatting arguments. + /// The value of the key in the current language. + string Translate(Request request, string key, params object[] args); + + /// + /// Translates a given key to the specified language. + /// + /// The culture with the language to use. + /// The internationalization key. + /// The value of the key in the current language. + string Translate(CultureInfo culture, string key); + + /// + /// Translates a given key to the specified language. + /// + /// The culture with the language to use. + /// The internationalization key. + /// The formatting arguments. + /// The value of the key in the current language. + string Translate(CultureInfo culture, string key, params object[] args); + + /// + /// Translates a given key to the specified language. + /// + /// The culture with the language to use. + /// The plugin id. + /// The internationalization key. + /// The value of the key in the current language. + string Translate(CultureInfo culture, string pluginId, string key); + + /// + /// Translates a given key to the specified language. + /// + /// The culture with the language to use. + /// The plugin id. + /// The internationalization key. + /// The formatting arguments. + /// The value of the key in the current language. + string Translate(CultureInfo culture, string pluginId, string key, params object[] args); + } +} diff --git a/src/WebExpress.WebCore/Internationalization/InternationalizationExtensions.cs b/src/WebExpress.WebCore/Internationalization/InternationalizationExtensions.cs deleted file mode 100644 index 6c02023..0000000 --- a/src/WebExpress.WebCore/Internationalization/InternationalizationExtensions.cs +++ /dev/null @@ -1,42 +0,0 @@ -using WebExpress.WebCore.WebApplication; - -namespace WebExpress.WebCore.Internationalization -{ - public static class InternationalizationExtensions - { - /// - /// Internationalization of a key. - /// - /// An internationalization object that is being extended. - /// The internationalization key. - /// The value of the key in the current language. - public static string I18N(this II18N obj, string key) - { - return InternationalizationManager.I18N(obj, key); - } - - /// - /// Internationalization of a key. - /// - /// An internationalization object that is being extended. - /// The plugin id. - /// The internationalization key. - /// The value of the key in the current language. - public static string I18N(this II18N obj, string pluginId, string key) - { - return InternationalizationManager.I18N(obj.Culture, pluginId, key); - } - - /// - /// Internationalization of a key. - /// - /// An internationalization object that is being extended. - /// The allication context. - /// The internationalization key. - /// The value of the key in the current language. - public static string I18N(this II18N obj, IApplicationContext applicationContext, string key) - { - return InternationalizationManager.I18N(obj.Culture, applicationContext?.PluginContext?.PluginId, key); - } - } -} diff --git a/src/WebExpress.WebCore/Internationalization/InternationalizationManager.cs b/src/WebExpress.WebCore/Internationalization/InternationalizationManager.cs index d582a8a..bc9ee28 100644 --- a/src/WebExpress.WebCore/Internationalization/InternationalizationManager.cs +++ b/src/WebExpress.WebCore/Internationalization/InternationalizationManager.cs @@ -3,8 +3,9 @@ using System.IO; using System.Linq; using System.Reflection; -using WebExpress.WebCore.WebMessage; +using WebExpress.WebCore.Internationalization.Model; using WebExpress.WebCore.WebComponent; +using WebExpress.WebCore.WebMessage; using WebExpress.WebCore.WebPlugin; namespace WebExpress.WebCore.Internationalization @@ -12,8 +13,10 @@ namespace WebExpress.WebCore.Internationalization /// /// Internationalization /// - public sealed class InternationalizationManager : IComponentPlugin, ISystemComponent + public sealed class InternationalizationManager : IInternationalizationManager, IComponentManagerPlugin, ISystemComponent { + private readonly IComponentHub _componentHub; + /// /// Returns the default language. /// @@ -22,7 +25,7 @@ public sealed class InternationalizationManager : IComponentPlugin, ISystemCompo /// /// Returns the directory by listing the internationalization key-value pairs. /// - private static InternationalizationDictionary Dictionary { get; } = new InternationalizationDictionary(); + private static InternationalizationDictionary Dictionary { get; } = []; /// /// Returns or sets the reference to the context of the host. @@ -30,33 +33,30 @@ public sealed class InternationalizationManager : IComponentPlugin, ISystemCompo public IHttpServerContext HttpServerContext { get; private set; } /// - /// Constructor + /// Initializes a new instance of the class. /// - internal InternationalizationManager() + /// The component hub. + /// The reference to the context of the host. + private InternationalizationManager(IComponentHub componentHub, IHttpServerContext httpServerContext) { - ComponentManager.PluginManager.AddPlugin += (sender, pluginContext) => + _componentHub = componentHub; + + _componentHub.PluginManager.AddPlugin += (sender, pluginContext) => { Register(pluginContext); }; - ComponentManager.PluginManager.RemovePlugin += (sender, pluginContext) => + _componentHub.PluginManager.RemovePlugin += (sender, pluginContext) => { Remove(pluginContext); }; - } - /// - /// Initialization - /// - /// The reference to the context of the host. - public void Initialization(IHttpServerContext context) - { - HttpServerContext = context; + HttpServerContext = httpServerContext; DefaultCulture = HttpServerContext.Culture; HttpServerContext.Log.Debug ( - I18N("webexpress:internationalizationmanager.initialization") + Translate("webexpress.webcore:internationalizationmanager.initialization") ); } @@ -67,11 +67,11 @@ public void Initialization(IHttpServerContext context) public void Register(IPluginContext pluginContext) { var pluginId = pluginContext.PluginId; - Register(pluginContext.Assembly, pluginId); + Register(pluginContext.Assembly, pluginId.ToString()); HttpServerContext.Log.Debug ( - I18N("webexpress:internationalizationmanager.register", pluginId) + Translate("webexpress.webcore:internationalizationmanager.register", pluginId) ); } @@ -92,22 +92,23 @@ public void Register(IEnumerable pluginContexts) /// /// The assembly that contains the key-value pairs to insert. /// The id of the plugin to which the internationalization data will be assigned. - internal static void Register(Assembly assembly, string pluginId) + public void Register(Assembly assembly, string pluginId) { var assemblyName = assembly.GetName().Name.ToLower(); var name = assemblyName + ".internationalization."; - var resources = assembly.GetManifestResourceNames().Where(x => x.ToLower().Contains(name)); + var resources = assembly.GetManifestResourceNames().Where(x => x.Contains(name, System.StringComparison.CurrentCultureIgnoreCase)); foreach (var languageResource in resources) { var language = languageResource.Split('.').LastOrDefault()?.ToLower(); - if (!Dictionary.ContainsKey(language)) + if (!Dictionary.TryGetValue(language, out InternationalizationItem value)) { - Dictionary.Add(language, new InternationalizationItem()); + value = ([]); + Dictionary.Add(language, value); } - var dictItem = Dictionary[language]; + var dictItem = value; using var stream = assembly.GetManifestResourceStream(languageResource); using var streamReader = new StreamReader(stream); @@ -126,6 +127,8 @@ internal static void Register(Assembly assembly, string pluginId) } } } + + Log(); } /// @@ -134,50 +137,99 @@ internal static void Register(Assembly assembly, string pluginId) /// The context of the plugin containing the key-value pairs to remove. public void Remove(IPluginContext pluginContext) { + if (pluginContext == null) + { + return; + } + foreach (var dictionary in Dictionary.Values) + { + var keysToRemove = dictionary.Keys.Where(k => k.StartsWith($"{pluginContext?.PluginId}:")).ToList(); + + foreach (var key in keysToRemove) + { + dictionary.Remove(key); + } + } + + Log(); + } + + /// + /// Translates a given key to the default language. + /// + /// The internationalization key. + /// The value of the key in the current language. + public string Translate(string key) + { + return Translate(DefaultCulture, null, key); } /// - /// Internationalization of a key. + /// Translates a given key to the default language. /// - /// An internationalization object that is being extended. /// The internationalization key. + /// The formatting arguments. /// The value of the key in the current language. - public static string I18N(II18N obj, string key) + public string Translate(string key, params object[] args) { - return I18N(obj.Culture, key); + return string.Format(Translate(DefaultCulture, null, key), args); } /// - /// Internationalization of a key. + /// Translates a given key to the specified language. /// /// The request with the language to use. /// The internationalization key. /// The value of the key in the current language. - public static string I18N(Request request, string key) + public string Translate(Request request, string key) { - return I18N(request.Culture, null, key); + return Translate(request.Culture, null, key); } /// - /// Internationalization of a key. + /// Translates a given key to the specified language. + /// + /// The request with the language to use. + /// The internationalization key. + /// The formatting arguments. + /// The value of the key in the current language. + public string Translate(Request request, string key, params object[] args) + { + return string.Format(Translate(request, key), args); + } + + /// + /// Translates a given key to the specified language. + /// + /// The culture with the language to use. + /// The internationalization key. + /// The value of the key in the current language. + public string Translate(CultureInfo culture, string key) + { + return Translate(culture, null, key); + } + + /// + /// Translates a given key to the specified language. /// /// The culture with the language to use. /// The internationalization key. + /// The formatting arguments. /// The value of the key in the current language. - public static string I18N(CultureInfo culture, string key) + public string Translate(CultureInfo culture, string key, params object[] args) { - return I18N(culture, null, key); + return string.Format(Translate(culture, key), args); } /// - /// Internationalization of a key. + /// Translates a given key to the specified language. /// /// The culture with the language to use. /// The plugin id. /// The internationalization key. /// The value of the key in the current language. - public static string I18N(CultureInfo culture, string pluginId, string key) + public string Translate(CultureInfo culture, string pluginId, string key) { var language = culture?.TwoLetterISOLanguageName; var k = string.IsNullOrWhiteSpace(key) || string.IsNullOrWhiteSpace(pluginId) || key.StartsWith($"{pluginId?.ToLower()}:") ? key?.ToLower() : $"{pluginId?.ToLower()}:{key?.ToLower()}"; @@ -192,46 +244,48 @@ public static string I18N(CultureInfo culture, string pluginId, string key) language = DefaultCulture?.TwoLetterISOLanguageName; } - var item = Dictionary[language]; + if (string.IsNullOrWhiteSpace(language)) + { + return key; + } - if (item.ContainsKey(k)) + if (Dictionary.TryGetValue(language, out InternationalizationItem item)) { - return item[k]; + if (item.TryGetValue(k, out string value)) + { + return value; + } } return key; } /// - /// Internationalization of a key. + /// Translates a given key to the specified language. /// + /// The culture with the language to use. + /// The plugin id. /// The internationalization key. + /// The formatting arguments. /// The value of the key in the current language. - public static string I18N(string key) + public string Translate(CultureInfo culture, string pluginId, string key, params object[] args) { - return I18N(DefaultCulture, null, key); + return string.Format(Translate(culture, pluginId, key), args); } /// - /// Internationalization of a key. + /// Information about the component is collected and prepared for output in the log. /// - /// The internationalization key. - /// The formatting arguments. - /// The value of the key in the current language. - public static string I18N(string key, params object[] args) + private void Log() { - return string.Format(I18N(DefaultCulture, null, key), args); + } /// - /// Information about the component is collected and prepared for output in the log. + /// Release of unmanaged resources reserved during use. /// - /// The context of the plugin. - /// A list of log entries. - /// The shaft deep. - public void PrepareForLog(IPluginContext pluginContext, IList output, int deep) + public void Dispose() { - } } } diff --git a/src/WebExpress.WebCore/Internationalization/InternationalizationDictionary.cs b/src/WebExpress.WebCore/Internationalization/Model/InternationalizationDictionary.cs similarity index 82% rename from src/WebExpress.WebCore/Internationalization/InternationalizationDictionary.cs rename to src/WebExpress.WebCore/Internationalization/Model/InternationalizationDictionary.cs index de064ec..ac4087b 100644 --- a/src/WebExpress.WebCore/Internationalization/InternationalizationDictionary.cs +++ b/src/WebExpress.WebCore/Internationalization/Model/InternationalizationDictionary.cs @@ -1,6 +1,6 @@ using System.Collections.Generic; -namespace WebExpress.WebCore.Internationalization +namespace WebExpress.WebCore.Internationalization.Model { /// /// key = language (ISO 639-1 two-letter) diff --git a/src/WebExpress.WebCore/Internationalization/InternationalizationItem.cs b/src/WebExpress.WebCore/Internationalization/Model/InternationalizationItem.cs similarity index 69% rename from src/WebExpress.WebCore/Internationalization/InternationalizationItem.cs rename to src/WebExpress.WebCore/Internationalization/Model/InternationalizationItem.cs index 9aaa614..40ede17 100644 --- a/src/WebExpress.WebCore/Internationalization/InternationalizationItem.cs +++ b/src/WebExpress.WebCore/Internationalization/Model/InternationalizationItem.cs @@ -1,6 +1,6 @@ using System.Collections.Generic; -namespace WebExpress.WebCore.Internationalization +namespace WebExpress.WebCore.Internationalization.Model { internal class InternationalizationItem : Dictionary { diff --git a/src/WebExpress.WebCore/Internationalization/de b/src/WebExpress.WebCore/Internationalization/de index 8880cbc..8d5479e 100644 --- a/src/WebExpress.WebCore/Internationalization/de +++ b/src/WebExpress.WebCore/Internationalization/de @@ -39,10 +39,15 @@ componentmanager.wrongtype=Der Typ '{0}' implementiert die Schnittstelle '{1}' n componentmanager.duplicate=Die Komponente '{0}' wurde bereits registriert. componentmanager.remove=Die Komponente '{0}' wurde entfernt. componentmanager.component=Komponenten: +componentmanager.name=Komponente: '{0}' + +logmanager.initialization=Der Logmanager wurde initialisiert. +logmanager.titel=Logmanager internationalizationmanager.initialization=Der Internationalisierungsmanager wurde initialisiert. internationalizationmanager.register=Das Plugin '{0}' wurde im Internationalizationmanager registriert. +packagemanager.titel=Paketmanager: packagemanager.initialization=Der Paketmanager wurde initialisiert. packagemanager.existing=Das Paket '{0}' ist im Katalog registriert. packagemanager.add=Das Paket '{0}' wird im Katalog neu aufgenommen. @@ -51,23 +56,26 @@ packagemanager.scan=Das Verzeichnis '{0}' wird auf neue Pakete gescannt. packagemanager.save=Der Katalog wird gespeichert. packagemanager.packagenotfound=Das Paket '{0}' wurde nicht im Dateisystem gefunden. packagemanager.boot.notfound=Das Plugin '{0}' ist nicht bekannt. +packagemanager.package=Package: '{0}' pluginmanager.initialization=Der Pluginmanager wurde initialisiert. pluginmanager.load={0}.dll wird geladen. Version = '{1}' pluginmanager.created=Das Plugin '{0}' wurde erstellt und im PluginManager registriert. pluginmanager.duplicate=Das Plugin '{0}' wurde bereits im PluginManager registriert. +pluginmanager.tomany=Es ist mehr als eine Pluginklasse vorhanden! Das Plugin '{0}' wird nicht geladen. pluginmanager.notavailable=Das Plugin '{0}' ist im PluginManager nicht vorhanden. pluginmanager.plugin=Plugin: '{0}' pluginmanager.pluginmanager.label=Plugin Manager: pluginmanager.pluginmanager.system=Systemplugin: '{0}' pluginmanager.pluginmanager.custom=Benutzerdefiniertes Plugin: '{0}' pluginmanager.pluginmanager.unfulfilleddependencies=Plugin mit unerfüllten Abhängigkeiten: '{0}' -pluginmanager.plugin.initialization=Das Plugin '{0}' wurde initialisiert. pluginmanager.plugin.processing.start=Das Plugin '{0}' wird ausgeführt. pluginmanager.plugin.processing.end=Die Ausführung des Plugin '{0}' wurde beendet. pluginmanager.fulfilleddependencies=Das Plugin '{0}' erfüllt alle Abhängigkeiten. pluginmanager.unfulfilleddependencies=Das Plugin '{0}' erfüllt eine Abhängigkeit zu dem Plugin '{1}' nicht. +pluginmanager.applicationless=Das Plugin '{0}' besitzt keine Angaben zur Anwendung. +applicationmanager.titel=Anwendungsmanager: applicationmanager.initialization=Der Anwendungsmanager wurde initialisiert. applicationmanager.register=Die Anwendung '{0}' wurde erstellt und im Anwendungsmanager registriert. applicationmanager.duplicate=Die Anwendung '{0}' wurde bereits im Anwendungsmanager registriert. @@ -77,55 +85,90 @@ applicationmanager.application.processing.start=Die Anwendung '{0}' wird ausgef applicationmanager.application.processing.end=Die Ausführung der Anwendung '{0}' wurde beendet. applicationmanager.application.boot.notfound=Das Plugin '{0}' ist nicht bekannt. -modulemanager.initialization=Der Modulmanager wurde initialisiert. -modulemanager.register=Das Modul '{0}' wurde der Anwendung '{1}' zugewiesen und im Modulmanager registriert. -modulemanager.duplicat=Das Modul '{0}' wurde bereits in der Anwendung '{1}' registriert. -modulemanager.applicationless=Das Modul '{0}' besitzt keine Angaben zur Anwendung. -modulemanager.module=Module: '{0}' für die Anwendung(n) '{1}' -modulemanager.module.initialization=Das Modul '{1}' der Anwendung {0} wurde initialisiert. -modulemanager.module.processing.start=Das Modul '{1}' der Anwendung {0} wird ausgeführt. -modulemanager.module.processing.end=Die Ausführung des Modul '{1}' der Anwendung {0} wurde beendet. - resourcemanager.initialization=Der Ressourcenmanager wurde initialisiert. -resourcemanager.register={0} Ressource(n) wurden dem Modul '{1}' zugewiesen. -resourcemanager.modulenotfound=Das Modul '{1}' wurde nicht gefunden, welches in der Ressource '{0}' angegeben wurde. -resourcemanager.moduleless=Die Ressource '{0}' besitzt keine Angaben zum Modul. -resourcemanager.addresource=Die Ressource '{0}' wurde in dem Modul '{1}' registiert. -resourcemanager.addresource.duplicate=Die Ressource '{0}' des Moduls '{1}' ist bereits hinzugefügt worden. -resourcemanager.addresource.error=Die Ressource '{0}' des Moduls '{1}' konnte nicht hinzugefügt werden. -resourcemanager.sitemap=Inhalt der Sitemap für das Modul '{0}': +resourcemanager.register={0} Ressource(n) wurden der Anwendung '{1}' zugewiesen. +resourcemanager.addresource=Die Ressource '{0}' wurde in der Anwendung '{1}' registiert. +resourcemanager.addresource.duplicate=Die Ressource '{0}' der Anwendung '{1}' ist bereits hinzugefügt worden. +resourcemanager.addresource.error=Die Ressource '{0}' der Anwendung '{1}' konnte nicht hinzugefügt werden. +resourcemanager.sitemap=Inhalt der Sitemap für die Anwendung '{0}': resourcemanager.wrongtype=Der Type '{0}' implementiert die Schnittstelle '{1}' nicht. -resourcemanager.resource=Ressource: '{0}' für das Modul '{1}' +resourcemanager.resource=Ressource: '{0}' für die Anwednung '{1}' + +assetmanager.initialization=Der Assetmanager wurde initialisiert. +assetmanager.addresource=Das Asset '{0}' wurde in der Anwendung '{1}' registiert. + +pagemanager.initialization=Der Pagemanager wurde initialisiert. +pagemanager.addpage=Die Seite '{0}' wurde in der Anwendung '{1}' registiert. +pagemanager.page=Seite: '{0}' für die Anwendung '{1}' -responsemanager.initialization=Der Responsemanagermanager wurde initialisiert. -responsemanager.register=Status {0} wurde im Module '{1}' registriert und der Statusseite '{2}' zugewiesen. -responsemanager.duplicat=Der Status {0} wurde bereits im Module '{1}' registriert registriert. Die Statusseite '{2}' wird daher nicht verwendet. -responsemanager.statuscode=Ein Statuscode wurde der Ressource '{1}' für das Modul '{0}' nicht zugewiesen. -responsemanager.statuspage=Statuscode: '{0}' +restapimanager.initialization=Der RestApiManager wurde initialisiert. +restapimanager.addrestapi=Die Seite '{0}' wurde in der Anwendung '{1}' registiert. +restapimanager.resource=Seite: '{0}' für die Anwendung '{1}' +restapimanager.methodnotsupported=Die Methode '{0}' wird nicht unterstützt. +statuspagemanager.titel=Statuspagemanager: +statuspagemanager.initialization=Der Statuspagemanager wurde initialisiert. +statuspagemanager.register=Der Status '{0}' wurde registriert und der Statusseite '{1}' zugewiesen. +statuspagemanager.duplicat=Der Status '{0}' wurde bereits registriert. Die Statusseite '{1}' wird daher nicht verwendet. +statuspagemanager.statuscodeless=Ein Statuscode wurde der Ressource '{1}' für die Anwendung '{0}' nicht zugewiesen. +statuspagemanager.statuspage=Statuscode: '{0}' + +sitemapmanager.titel=Sitemap: sitemapmanager.initialization=Der Sitemap-Manager wurde initialisiert. sitemapmanager.refresh=Die Sitemap wird neu aufgebaut. sitemapmanager.alreadyassigned=Der Knoten der Sitemap '{0}' ist bereits zugewiesen. Die Ressource '{1}' wird nicht in die Sitemap aufgenommen. sitemapmanager.addresource=Die Ressource '{0}' wurde in der Sitemap registriert. sitemapmanager.addresource.error=Die Ressource '{0}' konnte nicht in der Sitemap hinzugefügt werden. sitemapmanager.preorder={0} => {1} -sitemapmanager.sitemap=Sitemap: sitemapmanager.merge.error=Die beiden Sitemaps '{0}' und '{1}' konnten nicht gemerdged werden. sessionmanager.initialization=Der Sessionmanager wurde initialisiert. +settingpagemanager.initialization=Der Settingpagemanager wurde initialisiert. +settingpagemanager.register.category=Die Einstellungskategorie '{0}' wurde der Anwendung '{1}' zugewiesen und im Settingpagemanager registriert. +settingpagemanager.register.group=Die Einstellungsgruppe '{0}' wurde der Anwendung '{1}' zugewiesen und im Settingpagemanager registriert. +settingpagemanager.register.page=Die Einstellungsseite '{0}' wurde der Anwendung '{1}' zugewiesen und im Settingpagemanager registriert. +settingpagemanager.register.nocategory=Die Einstellungsseite '{0}' wurde keiner Einstellungskategorie zugewiesen. +settingpagemanager.register.nogroup=Die Einstellungsseite '{0}' wurde keiner Einstellungsgruppe zugewiesen. +settingpagemanager.category.general.name=Allgemeine Einstellungen +settingpagemanager.category.general.description=Standardkategorie für allgemeine Anwendungseinstellungen. +settingpagemanager.group.general.name=Allgemein +settingpagemanager.group.general.description=Allgemeine Anwendungseinstellungen. + +eventmanager.titel=Eventmanager: eventmanager.initialization=Der Eventmanager wurde initialisiert. - -jobmanager.initialization=Der Schedulemanager wurde initialisiert. -jobmanager.register=Das Plugin '{0}' wurde im Schedulemanager registriert. -jobmanager.job.register=Der Job '{1}' wurden dem Modul '{0}' zugewiesen. -jobmanager.moduleless=Der Job '{0}' besitzt keine Angaben zum Modul. -jobmanager.wrongmodule=Der Job '{1}' ist kein Teil des Moduls {0}. -jobmanager.job=Job: '{0}' für Modul '{1}' +eventmanager.register=Der Eventhandler '{0}' wurde der Anwendung '{1}' zugewiesen und im Eventmanager registriert. +eventmanager.duplicate=Der Eventhandler '{0}' wurde bereits in der Anwendung '{1}' registriert. +eventmanager.eventless=Der Eventhandler '{0}' besitzt keine Angaben zu einem Event. +eventmanager.handler=Eventhandler: '{0}' für die Anwendung '{1}'. + +jobmanager.initialization=Der Jobmanager wurde initialisiert. +jobmanager.register=Der Job '{0}' wurde der Anwendung '{1}' zugewiesen und im Jobmanager registriert. +jobmanager.duplicate=Der Job '{0}' wurde bereits in der Anwendung '{1}' registriert. +jobmanager.jobless=Der Job '{0}' besitzt keine Angaben zu einem Job. +jobmanager.job=Job: '{0}' für Anwendung '{1}' jobmanager.job.process=Der Job '{0}' wird ausgeführt. jobmanager.cron.parseerror=Syntaxfehler in der Zeitangabe eines Jobs. Der Wert '{0}' kann nicht verarbeitet werden. jobmanager.cron.range=Syntaxfehler in der Zeitangabe eines Jobs. Der Wert '{0}' ist außerhalb des gültigen Bereiches. +fragmentmanager.initialization=Der Fragmentmanager wurde initialisiert. +fragmentmanager.register=Das Fragment '{0}' wurde in der Sektion '{1}' der Anwendung '{2}' registriert. +fragmentmanager.error.section=Die Angabe der Sektion ist fehlerhaft. +fragmentmanager.wrongtype=Der Typ '{0}' implementiert die Schnittstelle '{1}' nicht. +fragmentmanager.addfragment.duplicate=Das Fragment '{0}' aus dem Plugin '{1}' ist bereits hinzugefügt worden. +fragmentmanager.titel=Fragmente: +fragmentmanager.fragment=Fragment: '{0}' für die Anwendung '{1}'. + +identitymanager.initialization=Der Identitymanager wurde initialisiert. +identitymanager.registerpermission=Die Berechtigung '{0}' wurde der Anwendung '{1}' zugewiesen und im Identitymanager registriert. +identitymanager.duplicatepermission=Die Berechtigung '{0}' wurde bereits in der Anwendung '{1}' registriert. +identitymanager.registerrole=Die Rolle '{0}' wurde der Anwendung '{1}' zugewiesen und im Identitymanager registriert. +identitymanager.duplicaterole=Die Rolle '{0}' wurde bereits in der Anwendung '{1}' registriert. + +thememanager.initialization=Der Thememanager wurde initialisiert. +thememanager.addtheme=Das Theme '{0}' wurde in der Anwendung '{1}' registiert. +thememanager.titel=Designvorlagen: +thememanager.theme=Designvorlage: '{0}' für die Anwendung '{1}'. + resource.variable.duplicate=Variable '{0}' bereits vorhanden! resource.file={0}: Datei '{1}' wurde geladen. - diff --git a/src/WebExpress.WebCore/Internationalization/en b/src/WebExpress.WebCore/Internationalization/en index 164d882..c2dcfb9 100644 --- a/src/WebExpress.WebCore/Internationalization/en +++ b/src/WebExpress.WebCore/Internationalization/en @@ -39,10 +39,15 @@ componentmanager.wrongtype=The type '{0}' does not implement the interface '{1}' componentmanager.duplicate=The component '{0}' has already been registered. componentmanager.remove=The component '{0}' has been removed. componentmanager.component=Components: +componentmanager.name=Component: '{0}' + +logmanager.initialization:The log manager has been initialized. +logmanager.titel:Log manager internationalizationmanager.initialization=The internationalization manager has been initialized. internationalizationmanager.register=The plugin '{0}' is registered in the internationalization manager. +packagemanager.titel=Package manager: packagemanager.initialization=The package manager has been initialized. packagemanager.existing=The package '{0}' is registered in the catalog. packagemanager.add=Package '{0}' is added to the catalog. @@ -51,23 +56,26 @@ packagemanager.scan=The directory '{0}' is scanned for new packages. packagemanager.save=The catalog is saved. packagemanager.packagenotfound=The package '{0}' was not found in the file system. packagemanager.boot.notfound=The plugin '{0}' is unknown. +packagemanager.package=Package: '{0}' pluginmanager.initialization=The plugin manager has been initialized. pluginmanager.load={0}.dll is loading. Version = '{1}' pluginmanager.created=The plugin '{0}' was created and registered in the plugin manager. pluginmanager.duplicate=The plugin '{0}' has already been registered in plugin manager. +pluginmanager.tomany=There is more than one plugin class! The plugin '{0}' is not loading. pluginmanager.notavailable=The plugin '{0}' does not exist in plugin manager. pluginmanager.plugin=Plugin: '{0}' pluginmanager.pluginmanager.label=Plugin manager: pluginmanager.pluginmanager.system=System plugin: '{0}' pluginmanager.pluginmanager.custom=custom plugin: '{0}' pluginmanager.pluginmanager.unfulfilleddependencies=Plugin with unfulfilled dependencies: '{0}' -pluginmanager.plugin.initialization=The plugin '{0}' has been initialized. pluginmanager.plugin.processing.start=The plugin '{0}' is running. pluginmanager.plugin.processing.end=The running of the plugin '{0}' has been stopped. pluginmanager.fulfilleddependencies=The plugin '{0}' fulfills all dependencies. pluginmanager.unfulfilleddependencies=The plugin '{0}' does not fulfill a dependency on the plugin '{1}'. +pluginmanager.applicationless=The plugin '{0}' does not have any information about an application. +applicationmanager.titel=Application manager: applicationmanager.initialization=The application manager has been initialized. applicationmanager.registerapplication=The application '{0}' has been created and registered in the application manager. applicationmanager.duplicateapplication=The application '{0}' has already been registered in the application manager. @@ -77,54 +85,90 @@ applicationmanager.application.processing.start=The application '{0}' is running applicationmanager.application.processing.end=The running of the application '{0}' has been stopped. applicationmanager.application.boot.notfound=The plugin '{0}' is unknown. -modulemanager.initialization=The module manager has been initialized. -modulemanager.register=The module '{0}' has been assigned to the application '{1}' and registered in the module manager. -modulemanager.duplicate=The module '{0}' has already been registered in the application '{1}'. -modulemanager.applicationless=The module '{0}' does not have any information about the application. -modulemanager.module=Module: '{0}' for application(s) '{1}' -pluginmanager.module.initialization=The module '{1}' of the application '{0}' has been initialized. -pluginmanager.module.processing.start=The module '{1}' of the application '{0}' is running. -pluginmanager.module.processing.end=The running of the module '{1}' of the application '{0}' has been stopped. - resourcemanager.initialization=The resource manager has been initialized. -resourcemanager.register={0} resource(s) have been assigned to the module '{1}'. -resourcemanager.modulenotfound=The module '{1}' could not be found, which was specified in the resource '{0}'. -resourcemanager.moduleless=The resource '{0}' does not have any information about the module. -resourcemanager.addresource=The resource '{0}' has been registered in the module '{1}'. -resourcemanager.addresource.duplicate=The resource '{0}' of module '{1}' has already been added. -resourcemanager.addresource.error=The resource '{0}' of the module '{1}' could not be added. +resourcemanager.register={0} resource(s) have been assigned to the application '{1}'. +resourcemanager.addresource=The resource '{0}' has been registered in the application '{1}'. +resourcemanager.addresource.duplicate=The resource '{0}' of application '{1}' has already been added. +resourcemanager.addresource.error=The resource '{0}' of the application '{1}' could not be added. resourcemanager.sitemap=Sitemap content resource '{0}' application: resourcemanager.wrongtype=The type '{0}' does not implement the interface '{1}'. -resourcemanager.statuspage=Resource: '{0}' for module '{1}' +resourcemanager.resource=Resource: '{0}' for application '{1}' + +assetmanager.initialization=The asset manager has been initialized. +assetmanager.addresource=The asset '{0}' has been registered in the application '{1}'. + +pagemanager.initialization=The page manager has been initialized. +pagemanager.addpage=The page '{0}' has been registered in the application '{1}'. +pagemanager.page=Page: '{0}' for application '{1}' + +restapimanager.initialization=The REST API manager has been initialized. +restapimanager.addrestapi=The REST API '{0}' has been registered in the application '{1}'. +restapimanager.resource=REST API: '{0}' for application '{1}' +restapimanager.methodnotsupported=The method '{0}' is not supported. -responsemanager.initialization=The response manager has been initialized. -responsemanager.register=Status {0} has been registered in the module '{1}' and assigned to the status page '{2}'. -responsemanager.duplicat=The status {0} has already been registered in the module '{1}'. Therefore, the status page '{2}' is not used. -responsemanager.statuscode=A status code has not been assigned to the resource '{1}' for the module '{0}'. -responsemanager.resource=Status code: '{0}' +statuspagemanager.titel=Status page manager: +statuspagemanager.initialization=The status page manager has been initialized. +statuspagemanager.register=The status '{0}' has been registered and assigned to the status page '{1}'. +statuspagemanager.duplicat=The status '{0}' has already been registered. Therefore, the status page '{1}' is not used. +statuspagemanager.statuscodeless=A status code has not been assigned to the resource '{1}' for the application '{0}'. +statuspagemanager.resource=Status code: '{0}' +sitemapmanager.titel=Sitemap: sitemapmanager.initialization=The sitemap manager has been initialized. sitemapmanager.refresh=The sitemap will be rebuilt. sitemapmanager.alreadyassigned=The node of the sitemap '{0}' is already assigned. The resource '{1}' is not included in the sitemap. sitemapmanager.addresource=The resource '{0}' has been registered in the sitemap. sitemapmanager.addresource.error=Could not add resource '{0}' in the sitemap. sitemapmanager.preorder={0} => {1} -sitemapmanager.sitemap=Sitemap: sitemapmanager.merge.error=The two sitemaps '{0}' and '{1}' could not be merged. sessionmanager.initialization=The session manager has been initialized. +settingpagemanager.initialization=The setting page manager has been initialized. +settingpagemanager.register.category=The setting category '{0}' has been registered in the application '{1}'. +settingpagemanager.register.group=The setting group '{0}' has been registered in the application '{1}'. +settingpagemanager.register.page=The setting page '{0}' has been registered in the application '{1}'. +settingpagemanager.register.nocategory=The setting page '{0}' has not been assigned to a setting category. +settingpagemanager.register.nogroup=The setting page '{0}' has not been assigned to a setting group. +settingpagemanager.category.general.name=General settings +settingpagemanager.category.general.description=Default category for general application settings. +settingpagemanager.group.general.name=General +settingpagemanager.group.general.description=General application settings. + +eventmanager.titel=Event manager: eventmanager.initialization=The event manager has been initialized. +eventmanager.register=The event handler '{0}' has been registered in the application '{1}'. +eventmanager.duplicate=The event handler '{0}' has already been registered. Therefore, the application '{1}' is not used. +eventmanager.eventless=The event handler '{0}' does not have any information about the event. +eventmanager.handler=Event handler: '{0}' for application '{1}'. jobmanager.initialization=The schedule manager has been initialized. -jobmanager.register=The plugin '{0}' is registered in the schedule manager manager. -jobmanager.job.register=The job '{1}' have been assigned to the module '{0}'. -jobmanager.moduleless=The job '{0}' does not have any information about the module. -jobmanager.wrongmodule=The '{1}' job is not part of the module {0}. -jobmanager.job=Job: '{0}' for module '{1}' +jobmanager.register=The job '{0}' has been registered in the application '{1}'. +jobmanager.duplicate=The job '{0}' has already been registered. Therefore, the application '{1}' is not used. +jobmanager.jobless=The job '{0}' does not have any information about the job. +jobmanager.job=Job: '{0}' for plugin '{1}' jobmanager.job.process=The job '{0}' is executed. jobmanager.cron.parseerror=Syntax error in the timing of a job. The value '{0}' cannot be processed. jobmanager.cron.range=Syntax error in the timing of a job. The value '{0}' is outside the valid range. +fragmentmanager.initialization=The fragment manager has been initialized. +fragmentmanager.register=The fragment '{0}' has been registered in the '{1}' section of application '{2}'. +fragmentmanager.error.section=The section is incorrect. +fragmentmanager.wrongtype=The type '{0}' does not implement the interface '{1}'. +fragmentmanager.addfragment.duplicate=The fragment '{0}' from the plugin '{1}' has already been added. +fragmentmanager.titel=Fragments: +fragmentmanager.fragment=Fragment: '{0}' for application '{1}'. + +identitymanager.initialization=The identity manager has been initialized. +identitymanager.registerpermission=The permission '{0}' has been assigned to the application '{1}' and registered in the identity manager. +identitymanager.duplicatepermission=The permission '{0}' has already been registered in the application '{1}'. +identitymanager.registerrole=The role '{0}' has been assigned to the application '{1}' and registered in the identity manager. +identitymanager.duplicaterole=The role '{0}' has already been registered in the application '{1}'. + +thememanager.initialization=The theme manager has been initialized. +thememanager.addtheme=The theme '{0}' has been registered in the application '{1}'. +thememanager.titel=Themes: +thememanager.theme=Theme: '{0}' for application '{1}'. + resource.variable.duplicate=Variable '{0}' already exists! -resource.file={0}: File '{1}' has been loaded. \ No newline at end of file +resource.file={0}: File '{1}' has been loaded. diff --git a/src/WebExpress.WebCore/Setting/ISettingItem.cs b/src/WebExpress.WebCore/Setting/ISettingItem.cs index 5a3ad8f..eb2fdd5 100644 --- a/src/WebExpress.WebCore/Setting/ISettingItem.cs +++ b/src/WebExpress.WebCore/Setting/ISettingItem.cs @@ -1,5 +1,8 @@ namespace WebExpress.WebCore.Setting { + /// + /// Interface for a settings object. + /// public interface ISettingItem { } diff --git a/src/WebExpress.WebCore/Setting/SettingLogItem.cs b/src/WebExpress.WebCore/Setting/SettingLogItem.cs index e72a49a..88c66ff 100644 --- a/src/WebExpress.WebCore/Setting/SettingLogItem.cs +++ b/src/WebExpress.WebCore/Setting/SettingLogItem.cs @@ -45,7 +45,7 @@ public class SettingLogItem : ISettingItem public string Timepattern { get; set; } /// - /// Constructor + /// Initializes a new instance of the class. /// public SettingLogItem() { diff --git a/src/WebExpress.WebCore/WebApplication/Application.cs b/src/WebExpress.WebCore/WebApplication/Application.cs new file mode 100644 index 0000000..98f63d6 --- /dev/null +++ b/src/WebExpress.WebCore/WebApplication/Application.cs @@ -0,0 +1,46 @@ +using System; + +namespace WebExpress.WebCore.WebApplication +{ + /// + /// This represents an application. + /// + public abstract class Application : IApplication + { + /// + /// Returns the context of the application. + /// + public IApplicationContext ApplicationContext { get; private set; } + + /// + /// Initializes a new instance of the class. + /// + public Application() + { + } + + /// + /// Initialization of the application. Here, for example, managed resources can be loaded. + /// + /// The context that applies to the execution of the application + public virtual void Initialization(IApplicationContext applicationContext) + { + ApplicationContext = applicationContext; + } + + /// + /// Called when the application starts working. The call is concurrent. + /// + public virtual void Run() + { + } + + /// + /// Release unmanaged resources that have been reserved during use. + /// + public virtual void Dispose() + { + GC.SuppressFinalize(this); + } + } +} diff --git a/src/WebExpress.WebCore/WebApplication/ApplicationContext.cs b/src/WebExpress.WebCore/WebApplication/ApplicationContext.cs index 9d58cda..14c97f1 100644 --- a/src/WebExpress.WebCore/WebApplication/ApplicationContext.cs +++ b/src/WebExpress.WebCore/WebApplication/ApplicationContext.cs @@ -1,9 +1,11 @@ -using System.Collections.Generic; +using WebExpress.WebCore.WebEndpoint; using WebExpress.WebCore.WebPlugin; -using WebExpress.WebCore.WebUri; namespace WebExpress.WebCore.WebApplication { + /// + /// Represents the context of an application. + /// public class ApplicationContext : IApplicationContext { /// @@ -26,11 +28,6 @@ public class ApplicationContext : IApplicationContext /// public string Description { get; internal set; } - /// - /// Returns an enumeration of options. Options enable optional resources. - /// - public IEnumerable Options { get; internal set; } - /// /// Returns the asset directory. This is mounted in the asset directory of the server. /// @@ -44,27 +41,27 @@ public class ApplicationContext : IApplicationContext /// /// Returns the context path. This is mounted in the context path of the server. /// - public UriResource ContextPath { get; internal set; } + public IRoute ContextPath { get; internal set; } /// /// Returns the icon uri. /// - public UriResource Icon { get; internal set; } + public IRoute Icon { get; internal set; } /// - /// Constructor + /// Initializes a new instance of the class. /// public ApplicationContext() { } /// - /// Conversion of the apllication context into its string representation. + /// Conversion of the application context into its string representation. /// /// The string that uniquely represents the application. public override string ToString() { - return $"Application {ApplicationId}"; + return $"Application: {ApplicationId}"; } } } diff --git a/src/WebExpress.WebCore/WebApplication/ApplicationDictionary.cs b/src/WebExpress.WebCore/WebApplication/ApplicationDictionary.cs deleted file mode 100644 index b6d1622..0000000 --- a/src/WebExpress.WebCore/WebApplication/ApplicationDictionary.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System.Collections.Generic; -using WebExpress.WebCore.WebPlugin; - -namespace WebExpress.WebCore.WebApplication -{ - /// - /// Key = Plugin context - /// Value = { Key = application id, Value = application item } - /// - internal class ApplicationDictionary : Dictionary> - { - } -} diff --git a/src/WebExpress.WebCore/WebApplication/ApplicationManager.cs b/src/WebExpress.WebCore/WebApplication/ApplicationManager.cs index ff23a8a..139ceb7 100644 --- a/src/WebExpress.WebCore/WebApplication/ApplicationManager.cs +++ b/src/WebExpress.WebCore/WebApplication/ApplicationManager.cs @@ -1,22 +1,29 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Text.RegularExpressions; using System.Threading.Tasks; using WebExpress.WebCore.Internationalization; +using WebExpress.WebCore.WebApplication.Model; using WebExpress.WebCore.WebAttribute; using WebExpress.WebCore.WebComponent; +using WebExpress.WebCore.WebEndpoint; +using WebExpress.WebCore.WebLog; using WebExpress.WebCore.WebPlugin; -using WebExpress.WebCore.WebUri; namespace WebExpress.WebCore.WebApplication { /// /// Management of WebExpress applications. /// - public sealed class ApplicationManager : IComponentPlugin, IExecutableElements, ISystemComponent + public sealed class ApplicationManager : IApplicationManager, IExecutableElements, ISystemComponent { + private readonly IComponentHub _componentHub; + private readonly IHttpServerContext _httpServerContext; + private readonly ApplicationDictionary _dictionary = new(); + /// /// An event that fires when an application is added. /// @@ -27,48 +34,29 @@ public sealed class ApplicationManager : IComponentPlugin, IExecutableElements, /// public event EventHandler RemoveApplication; - /// - /// Returns or sets the reference to the context of the host. - /// - public IHttpServerContext HttpServerContext { get; private set; } - - /// - /// Returns or sets the directory where the applications are listed. - /// - private ApplicationDictionary Dictionary { get; } = new ApplicationDictionary(); - /// /// Returns the stored applications. /// - public IEnumerable Applications => Dictionary.Values.SelectMany(x => x.Values).Select(x => x.ApplicationContext); + public IEnumerable Applications => _dictionary.All; /// - /// Constructor + /// Initializes a new instance of the class. /// - internal ApplicationManager() + /// The component hub. + /// The reference to the context of the host. + [SuppressMessage("CodeQuality", "IDE0051:Remove unused private members", Justification = "Used via Reflection.")] + private ApplicationManager(IComponentHub componentHub, IHttpServerContext httpServerContext) { - ComponentManager.PluginManager.AddPlugin += (sender, pluginContext) => - { - Register(pluginContext); - }; + _componentHub = componentHub; - ComponentManager.PluginManager.RemovePlugin += (sender, pluginContext) => - { - Remove(pluginContext); - }; - } + _componentHub.PluginManager.AddPlugin += OnAddPlugin; + _componentHub.PluginManager.RemovePlugin += OnRemovePlugin; - /// - /// Initialization - /// - /// The reference to the context of the host. - public void Initialization(IHttpServerContext context) - { - HttpServerContext = context; + _httpServerContext = httpServerContext; - HttpServerContext.Log.Debug + _httpServerContext.Log.Debug ( - InternationalizationManager.I18N("webexpress:applicationmanager.initialization") + I18N.Translate("webexpress.webcore:applicationmanager.initialization") ); } @@ -76,18 +64,15 @@ public void Initialization(IHttpServerContext context) /// Discovers and registers applications from the specified plugin. /// /// A context of a plugin whose applications are to be registered. - public void Register(IPluginContext pluginContext) + private void Register(IPluginContext pluginContext) { // the plugin has already been registered - if (Dictionary.ContainsKey(pluginContext)) + if (_dictionary.Contains(pluginContext)) { return; } - Dictionary.Add(pluginContext, new Dictionary()); - var assembly = pluginContext.Assembly; - var pluginDict = Dictionary[pluginContext]; foreach (var type in assembly.GetExportedTypes().Where ( @@ -102,9 +87,8 @@ public void Register(IPluginContext pluginContext) var icon = string.Empty; var description = string.Empty; var contextPath = string.Empty; - var assetPath = Path.DirectorySeparatorChar.ToString(); - var dataPath = Path.DirectorySeparatorChar.ToString(); - var options = new List(); + var assetPath = "/"; + var dataPath = "/"; // determining attributes foreach (var customAttribute in type.CustomAttributes @@ -134,23 +118,6 @@ public void Register(IPluginContext pluginContext) { dataPath = customAttribute.ConstructorArguments.FirstOrDefault().Value?.ToString(); } - else if (customAttribute.AttributeType == typeof(OptionAttribute)) - { - var value = customAttribute.ConstructorArguments.FirstOrDefault().Value.ToString().ToLower().Trim(); - options.Add(value); - } - else if (customAttribute.AttributeType.Name == typeof(WebExOptionAttribute<>).Name && customAttribute.AttributeType.Namespace == typeof(WebExOptionAttribute<>).Namespace) - { - var value = customAttribute.AttributeType.GenericTypeArguments.FirstOrDefault()?.FullName?.ToLower(); - options.Add(value); - } - else if (customAttribute.AttributeType.Name == typeof(WebExOptionAttribute<,>).Name && customAttribute.AttributeType.Namespace == typeof(WebExOptionAttribute<,>).Namespace) - { - var firstValue = customAttribute.AttributeType.GenericTypeArguments.FirstOrDefault()?.FullName?.ToLower(); - var secoundValue = customAttribute.AttributeType.GenericTypeArguments.LastOrDefault()?.FullName?.ToLower(); - - options.Add($"{firstValue}.{secoundValue}"); - } } // creating application context @@ -160,27 +127,31 @@ public void Register(IPluginContext pluginContext) ApplicationId = id, ApplicationName = name, Description = description, - Options = options, - AssetPath = Path.Combine(HttpServerContext.AssetPath, assetPath), - DataPath = Path.Combine(HttpServerContext.DataPath, dataPath), - Icon = UriResource.Combine(HttpServerContext.ContextPath, contextPath, icon), - ContextPath = UriResource.Combine(HttpServerContext.ContextPath, contextPath) + AssetPath = Path.Combine(_httpServerContext.AssetPath, assetPath), + DataPath = Path.Combine(_httpServerContext.DataPath, dataPath), + Icon = RouteEndpoint.Combine(_httpServerContext.ContextPath, contextPath, icon), + ContextPath = RouteEndpoint.Combine(_httpServerContext.ContextPath, contextPath) }; // create application - var applicationInstance = Activator.CreateInstance(type) as IApplication; + var applicationInstance = ComponentActivator.CreateInstance + ( + type, + applicationContext, + _httpServerContext, + _componentHub + ); - if (!pluginDict.ContainsKey(id)) + if (_dictionary.AddApplication(pluginContext, new ApplicationItem() { - pluginDict.Add(id, new ApplicationItem() - { - ApplicationContext = applicationContext, - Application = applicationInstance - }); - - HttpServerContext.Log.Debug + ApplicationClass = type, + ApplicationContext = applicationContext, + Application = applicationInstance + })) + { + _httpServerContext.Log.Debug ( - InternationalizationManager.I18N("webexpress:applicationmanager.register", id) + I18N.Translate("webexpress.webcore:applicationmanager.register", id) ); // raises the AddApplication event @@ -188,54 +159,61 @@ public void Register(IPluginContext pluginContext) } else { - HttpServerContext.Log.Warning + _httpServerContext.Log.Warning ( - InternationalizationManager.I18N("webexpress:applicationmanager.duplicate", id) + I18N.Translate("webexpress.webcore:applicationmanager.duplicate", id) ); } } + + Log(); } /// - /// Discovers and registers applications from the specified plugin. + /// Removes all applications associated with the specified plugin context. /// - /// A list with plugin contexts that contain the applications. - public void Register(IEnumerable pluginContexts) + /// The context of the plugin that contains the applications to remove. + internal void Remove(IPluginContext pluginContext) { - foreach (var pluginContext in pluginContexts) + if (pluginContext == null) + { + return; + } + + foreach (var applicationContext in _dictionary.RemoveApplications(pluginContext)) { - Register(pluginContext); + OnRemoveApplication(applicationContext); } + + Log(); } /// - /// Determines the application contexts for a given application id. + /// Returns the application context for a given application id. /// /// The application id. - /// The context of the application or null. - public IApplicationContext GetApplcation(string applicationId) + /// The context of the application or null if the application id is null, empty, or not found. + public IApplicationContext GetApplication(string applicationId) { - if (string.IsNullOrWhiteSpace(applicationId)) return null; - - var items = Dictionary.Values - .Where(x => x.ContainsKey(applicationId.ToLower())) - .Select(x => x[applicationId.ToLower()]) - .FirstOrDefault(); - - if (items != null) - { - return items.ApplicationContext; - } + return _dictionary.GetApplication(applicationId); + } - return null; + /// + /// Returns the application contexts for a given application id. + /// + /// The application type. + /// The context of the application or null. + public IApplicationContext GetApplication() + { + return GetApplications(typeof(T)).FirstOrDefault(); } /// - /// Determines the application contexts for the given application ids. + /// Returns the application contexts for the given application ids. /// /// The applications ids. Can contain regular expressions or * for all. /// The contexts of the applications as an enumeration. - public IEnumerable GetApplcations(IEnumerable applicationIds) + public IEnumerable GetApplications(IEnumerable applicationIds) { var list = new List(); @@ -263,18 +241,23 @@ public IEnumerable GetApplcations(IEnumerable appli } /// - /// Determines the application contexts for the given plugin. + /// Returns the application contexts for the given plugin. /// /// The context of the plugin. /// The contexts of the applications as an enumeration. - public IEnumerable GetApplcations(IPluginContext pluginContext) + public IEnumerable GetApplications(IPluginContext pluginContext) { - if (!Dictionary.ContainsKey(pluginContext)) - { - return new List(); - } + return _dictionary.GetApplications(pluginContext); + } - return Dictionary[pluginContext].Values.Select(x => x.ApplicationContext); + /// + /// Returns the application contexts for a given application type. + /// + /// The application type. + /// The contexts of the applications as an enumeration. + public IEnumerable GetApplications(Type application) + { + return _dictionary.GetApplications(application); } /// @@ -287,14 +270,13 @@ public void Boot(IPluginContext pluginContext) { return; } - - if (!Dictionary.ContainsKey(pluginContext)) + else if (!_dictionary.Contains(pluginContext)) { - HttpServerContext.Log.Warning + _httpServerContext.Log.Warning ( - InternationalizationManager.I18N + I18N.Translate ( - "webexpress:applicationmanager.application.boot.notfound", + "webexpress.webcore:applicationmanager.application.boot.notfound", pluginContext.PluginId ) ); @@ -302,39 +284,28 @@ public void Boot(IPluginContext pluginContext) return; } - foreach (var applicationItem in Dictionary[pluginContext]?.Values ?? Enumerable.Empty()) + foreach (var applicationItem in _dictionary.GetApplicationItems(pluginContext)) { var token = applicationItem.CancellationTokenSource.Token; - // Initialize application - applicationItem.Application.Initialization(applicationItem.ApplicationContext); - HttpServerContext.Log.Debug - ( - InternationalizationManager.I18N - ( - "webexpress:applicationmanager.application.initialization", - applicationItem.ApplicationContext.ApplicationId - ) - ); - // Run the application concurrently Task.Run(() => { - HttpServerContext.Log.Debug + _httpServerContext.Log.Debug ( - InternationalizationManager.I18N + I18N.Translate ( - "webexpress:applicationmanager.application.processing.start", + "webexpress.webcore:applicationmanager.application.processing.start", applicationItem.ApplicationContext.ApplicationId) ); applicationItem.Application.Run(); - HttpServerContext.Log.Debug + _httpServerContext.Log.Debug ( - InternationalizationManager.I18N + I18N.Translate ( - "webexpress:applicationmanager.application.processing.end", + "webexpress.webcore:applicationmanager.application.processing.end", applicationItem.ApplicationContext.ApplicationId ) ); @@ -350,31 +321,12 @@ public void Boot(IPluginContext pluginContext) /// The context of the plugin that contains the applications. public void ShutDown(IPluginContext pluginContext) { - foreach (var applicationItem in Dictionary[pluginContext]?.Values ?? Enumerable.Empty()) + foreach (var applicationItem in _dictionary.GetApplicationItems(pluginContext)) { applicationItem.CancellationTokenSource.Cancel(); } } - /// - /// Removes all applications associated with the specified plugin context. - /// - /// The context of the plugin that contains the applications to remove. - public void Remove(IPluginContext pluginContext) - { - if (!Dictionary.ContainsKey(pluginContext)) - { - return; - } - - foreach (var applicationContext in Dictionary[pluginContext]) - { - OnRemoveApplication(applicationContext.Value.ApplicationContext); - } - - Dictionary.Remove(pluginContext); - } - /// /// Raises the AddApplication event. /// @@ -393,22 +345,61 @@ private void OnRemoveApplication(IApplicationContext applicationContext) RemoveApplication?.Invoke(this, applicationContext); } + /// + /// Raises the event when an plugin is added. + /// + /// The source of the event. + /// The context of the plugin being added. + private void OnAddPlugin(object sender, IPluginContext e) + { + Register(e); + } + + /// + /// Raises the event when a plugin is removed. + /// + /// The source of the event. + /// The context of the plugin being removed. + private void OnRemovePlugin(object sender, IPluginContext e) + { + Remove(e); + } + /// /// Information about the component is collected and prepared for output in the log. /// - /// The context of the plugin. - /// A list of log entries. - /// The shaft deep. - public void PrepareForLog(IPluginContext pluginContext, IList output, int deep) + private void Log() { - foreach (var applicationContext in GetApplcations(pluginContext)) + if (!Applications.Any()) + { + return; + } + + using var frame = new LogFrameSimple(_httpServerContext.Log); + var list = new List + { + I18N.Translate("webexpress.webcore:applicationmanager.titel") + }; + + foreach (var applicationContext in Applications) { - output.Add + list.Add ( - string.Empty.PadRight(deep) + - InternationalizationManager.I18N("webexpress:applicationmanager.application", applicationContext.ApplicationId) + string.Empty.PadRight(2) + + I18N.Translate("webexpress.webcore:applicationmanager.application", applicationContext.ApplicationId) ); } + + _httpServerContext.Log.Info(string.Join(Environment.NewLine, list)); + } + + /// + /// Release of unmanaged resources reserved during use. + /// + public void Dispose() + { + _componentHub.PluginManager.AddPlugin -= OnAddPlugin; + _componentHub.PluginManager.RemovePlugin -= OnRemovePlugin; } } } diff --git a/src/WebExpress.WebCore/WebApplication/IApplication.cs b/src/WebExpress.WebCore/WebApplication/IApplication.cs index 18e2fd2..4ea4ddc 100644 --- a/src/WebExpress.WebCore/WebApplication/IApplication.cs +++ b/src/WebExpress.WebCore/WebApplication/IApplication.cs @@ -1,18 +1,12 @@ -using System; +using WebExpress.WebCore.WebComponent; namespace WebExpress.WebCore.WebApplication { /// /// This interface represents an application. /// - public interface IApplication : IDisposable + public interface IApplication : IComponent { - /// - /// Initialization of the application . - /// - /// The context. - void Initialization(IApplicationContext context); - /// /// Called when the application starts working. The call is concurrent. /// diff --git a/src/WebExpress.WebCore/WebApplication/IApplicationContext.cs b/src/WebExpress.WebCore/WebApplication/IApplicationContext.cs index e4f50e9..23c2062 100644 --- a/src/WebExpress.WebCore/WebApplication/IApplicationContext.cs +++ b/src/WebExpress.WebCore/WebApplication/IApplicationContext.cs @@ -1,13 +1,13 @@ -using System.Collections.Generic; +using WebExpress.WebCore.WebComponent; +using WebExpress.WebCore.WebEndpoint; using WebExpress.WebCore.WebPlugin; -using WebExpress.WebCore.WebUri; namespace WebExpress.WebCore.WebApplication { /// /// The application context. /// - public interface IApplicationContext + public interface IApplicationContext : IContext { /// /// Provides the context of the associated plugin. @@ -29,11 +29,6 @@ public interface IApplicationContext /// string Description { get; } - /// - /// Returns an enumeration of options. Options enable optional resources. - /// - IEnumerable Options { get; } - /// /// Returns the asset directory. This is mounted in the asset directory of the server. /// @@ -47,11 +42,11 @@ public interface IApplicationContext /// /// Returns the context path. This is mounted in the context path of the server. /// - UriResource ContextPath { get; } + IRoute ContextPath { get; } /// /// Returns the icon uri. /// - UriResource Icon { get; } + IRoute Icon { get; } } } diff --git a/src/WebExpress.WebCore/WebApplication/IApplicationManager.cs b/src/WebExpress.WebCore/WebApplication/IApplicationManager.cs new file mode 100644 index 0000000..ed01437 --- /dev/null +++ b/src/WebExpress.WebCore/WebApplication/IApplicationManager.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; +using WebExpress.WebCore.WebComponent; +using WebExpress.WebCore.WebPlugin; + +namespace WebExpress.WebCore.WebApplication +{ + /// + /// Interface of the management of WebExpress applications. + /// + public interface IApplicationManager : IComponentManager + { + /// + /// An event that fires when an application is added. + /// + event EventHandler AddApplication; + + /// + /// An event that fires when an application is removed. + /// + event EventHandler RemoveApplication; + + /// + /// Returns the stored applications. + /// + IEnumerable Applications { get; } + + /// + /// Returns the application contexts for a given application id. + /// + /// The application id. + /// The context of the application or null. + IApplicationContext GetApplication(string applicationId); + + /// + /// Returns the application contexts for a given application id. + /// + /// The application type. + /// The context of the application or null. + IApplicationContext GetApplication(); + + /// + /// Returns the application contexts for the given application ids. + /// + /// The applications ids. Can contain regular expressions or * for all. + /// The contexts of the applications as an enumeration. + IEnumerable GetApplications(IEnumerable applicationIds); + + /// + /// Returns the application contexts for the given plugin. + /// + /// The context of the plugin. + /// The contexts of the applications as an enumeration. + IEnumerable GetApplications(IPluginContext pluginContext); + + /// + /// Returns the application contexts for a given application type. + /// + /// The application type. + /// The contexts of the applications as an enumeration. + IEnumerable GetApplications(Type application); + } +} diff --git a/src/WebExpress.WebCore/WebApplication/Model/ApplicationDictionary.cs b/src/WebExpress.WebCore/WebApplication/Model/ApplicationDictionary.cs new file mode 100644 index 0000000..d1cff54 --- /dev/null +++ b/src/WebExpress.WebCore/WebApplication/Model/ApplicationDictionary.cs @@ -0,0 +1,130 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using WebExpress.WebCore.WebPlugin; + +namespace WebExpress.WebCore.WebApplication.Model +{ + /// + /// Represents a dictionary that maps plugin contexts to application items. + /// + internal class ApplicationDictionary + { + private readonly Dictionary> _dict = []; + + /// + /// Returns all application contexts from the dictionary. + /// + public IEnumerable All => _dict + .Values.SelectMany(x => x.Values) + .Select(x => x.ApplicationContext); + + /// + /// Adds a application item to the dictionary. + /// + /// The plugin context. + /// The application item. + /// True if the application item was added successfully, false if an element with the same status code already exists. + public bool AddApplication(IPluginContext pluginContext, ApplicationItem applicationItem) + { + if (!_dict.TryGetValue(pluginContext, out var applicationDict)) + { + applicationDict = []; + _dict[pluginContext] = applicationDict; + } + + if (applicationDict.TryAdd(applicationItem.ApplicationContext.ApplicationId, applicationItem)) + { + return true; + } + + return false; + } + + /// + /// Removes applications from the dictionary. + /// + /// The plugin context. + /// An IEnumerable of application contexts that were removed. + public IEnumerable RemoveApplications(IPluginContext pluginContext) + { + var applicationContexts = GetApplications(pluginContext); + + _dict.Remove(pluginContext); + + return applicationContexts; + } + + /// + /// Returns the application contexts for a given plugin context. + /// + /// The plugin context. + /// An IEnumerable of application contexts associated with the given plugin context. + public IEnumerable GetApplicationItems(IPluginContext pluginContext) + { + var applicationItems = _dict + .Where(x => x.Key == pluginContext) + .Select(x => x.Value) + .Select(x => x.Values) + .SelectMany(x => x); + + return applicationItems; + } + + /// + /// Returns the application contexts for a given plugin context. + /// + /// The plugin context. + /// An IEnumerable of application contexts associated with the given plugin context. + public IEnumerable GetApplications(IPluginContext pluginContext) + { + var applicationContexts = GetApplicationItems(pluginContext) + .Select(x => x.ApplicationContext); + + return applicationContexts; + } + + /// + /// Returns the application context for a given application id. + /// + /// The application id. + /// The context of the application or null if the application id is null, empty, or not found. + public IApplicationContext GetApplication(string applicationId) + { + if (string.IsNullOrWhiteSpace(applicationId)) return null; + + var items = _dict.Values + .Where(x => x.ContainsKey(applicationId.ToLower())) + .Select(x => x[applicationId.ToLower()]) + .FirstOrDefault(); + + return items?.ApplicationContext; + } + + /// + /// Returns the application contexts for a given application type. + /// + /// The application type. + /// The contexts of the applications as an enumeration. + public IEnumerable GetApplications(Type application) + { + if (application == null) return []; + + var items = _dict.Values.SelectMany(x => x.Values) + .Where(x => x.ApplicationClass.Equals(application) || application.IsAssignableFrom(x.ApplicationClass)) + .Select(x => x.ApplicationContext); + + return items; + } + + /// + /// Checks if the dictionary contains the specified plugin context. + /// + /// The plugin context to check for. + /// True if the plugin context exists in the dictionary, otherwise false. + public bool Contains(IPluginContext pluginContext) + { + return _dict.ContainsKey(pluginContext); + } + } +} diff --git a/src/WebExpress.WebCore/WebApplication/ApplicationItem.cs b/src/WebExpress.WebCore/WebApplication/Model/ApplicationItem.cs similarity index 72% rename from src/WebExpress.WebCore/WebApplication/ApplicationItem.cs rename to src/WebExpress.WebCore/WebApplication/Model/ApplicationItem.cs index d6cca18..2ed8b64 100644 --- a/src/WebExpress.WebCore/WebApplication/ApplicationItem.cs +++ b/src/WebExpress.WebCore/WebApplication/Model/ApplicationItem.cs @@ -1,6 +1,7 @@ -using System.Threading; +using System; +using System.Threading; -namespace WebExpress.WebCore.WebApplication +namespace WebExpress.WebCore.WebApplication.Model { /// /// Represents an application entry in the application directory. @@ -12,6 +13,11 @@ internal class ApplicationItem /// public IApplicationContext ApplicationContext { get; set; } + /// + /// Returns the application class. + /// + public Type ApplicationClass { get; internal set; } + /// /// The application. /// diff --git a/src/WebExpress.WebCore/WebAsset/Asset.cs b/src/WebExpress.WebCore/WebAsset/Asset.cs new file mode 100644 index 0000000..e554e83 --- /dev/null +++ b/src/WebExpress.WebCore/WebAsset/Asset.cs @@ -0,0 +1,164 @@ +using System; +using System.IO; +using System.Reflection; +using WebExpress.WebCore.Internationalization; +using WebExpress.WebCore.WebComponent; +using WebExpress.WebCore.WebMessage; + +namespace WebExpress.WebCore.WebAsset +{ + /// + /// Delivery of a resource embedded in the assembly. + /// + public class Asset : IAsset + { + private readonly IComponentHub _componentHub; + private readonly IAssetContext _assetContext; + private readonly IHttpServerContext _httpServerContext; + private readonly string _embeddedResource; + private byte[] _data; + + /// + /// Returns the root directory. + /// + public string AssetDirectory { get; protected set; } + + /// + /// Initializes a new instance of the class. + /// + /// The component hub. + /// The asset context. + /// The server context. + /// The embedded resource name. + public Asset(IComponentHub componentHub, IAssetContext assetContext, IHttpServerContext httpServerContext, string embeddedResource) + { + _componentHub = componentHub; + _assetContext = assetContext; + _httpServerContext = httpServerContext; + _embeddedResource = embeddedResource; + + var assembly = _assetContext.PluginContext.Assembly; + _data = GetData(assembly); + } + + /// + /// Processing of the resource. + /// + /// The request. + /// The response. + public Response Process(Request request) + { + if (_data == null) + { + return new ResponseNotFound(); + } + + var extension = Path.GetExtension(_assetContext.EndpointId.ToString())?.ToLower() ?? ""; + var response = new ResponseOK(); + + response.Header.CacheControl = "public, max-age=31536000"; + response.Header.ContentLength = _data.Length; + response.Content = _data; + + switch (extension) + { + case ".pdf": + response.Header.ContentType = "application/pdf"; + break; + case ".txt": + response.Header.ContentType = "text/plain"; + break; + case ".css": + response.Header.ContentType = "text/css"; + break; + case ".js": + response.Header.ContentType = "application/javascript"; + break; + case ".xml": + response.Header.ContentType = "text/xml"; + break; + case ".html": + case ".htm": + response.Header.ContentType = "text/html"; + break; + case ".zip": + response.Header.ContentDisposition = "attatchment; filename=" + _assetContext.EndpointId + "; size=" + _data.LongLength; + response.Header.ContentType = "application/zip"; + break; + case ".doc": + case ".docx": + response.Header.ContentType = "application/msword"; + break; + case ".xls": + case ".xlx": + response.Header.ContentType = "application/vnd.ms-excel"; + break; + case ".ppt": + response.Header.ContentType = "application/vnd.ms-powerpoint"; + break; + case ".gif": + response.Header.ContentType = "image/gif"; + break; + case ".png": + response.Header.ContentType = "image/png"; + break; + case ".svg": + response.Header.ContentType = "image/svg+xml"; + break; + case ".jpeg": + case ".jpg": + response.Header.ContentType = "image/jpg"; + break; + case ".ico": + response.Header.ContentType = "image/x-icon"; + break; + case ".mp3": + response.Header.ContentType = "audio/mpeg"; + break; + case ".mp4": + response.Header.ContentType = "video/mp4"; + break; + default: + response.Header.ContentType = "binary/octet-stream"; + break; + } + + _httpServerContext.Log.Debug(I18N.Translate + ( + "webexpress.webcore:asset.file", + request.RemoteEndPoint, request.Uri + )); + + return response; + } + + /// + /// Reads the data of a specified resource. + /// + /// The assembly. + /// The data. + private byte[] GetData(Assembly assembly) + { + if (assembly == null || _embeddedResource == null) + { + return []; + } + + using var stream = assembly.GetManifestResourceStream(_embeddedResource); + using var memoryStream = new MemoryStream(); + stream.CopyTo(memoryStream); + + return memoryStream.ToArray(); + } + + /// + /// Performs application-specific tasks related to sharing, returning, or resetting unmanaged resources. + /// + public void Dispose() + { + _data = null; + + GC.SuppressFinalize(this); + } + } +} \ No newline at end of file diff --git a/src/WebExpress.WebCore/WebAsset/AssetContext.cs b/src/WebExpress.WebCore/WebAsset/AssetContext.cs new file mode 100644 index 0000000..f8b15a0 --- /dev/null +++ b/src/WebExpress.WebCore/WebAsset/AssetContext.cs @@ -0,0 +1,72 @@ +using System; +using System.Collections.Generic; +using WebExpress.WebCore.WebApplication; +using WebExpress.WebCore.WebComponent; +using WebExpress.WebCore.WebCondition; +using WebExpress.WebCore.WebEndpoint; +using WebExpress.WebCore.WebPlugin; + +namespace WebExpress.WebCore.WebAsset +{ + /// + /// Represents the context of a asset. + /// + public class AssetContext : IAssetContext + { + /// + /// Returns the associated plugin context. + /// + public IPluginContext PluginContext { get; internal set; } + + /// + /// Returns the corresponding application context. + /// + public IApplicationContext ApplicationContext { get; internal set; } + + /// + /// Returns the conditions that must be met for the resource to be active. + /// + public IEnumerable Conditions => []; + + /// + /// Returns the resource id. + /// + public IComponentId EndpointId { get; internal set; } + + /// + /// Returns whether the resource is created once and reused each time it is called. + /// + public bool Cache => true; + + /// + /// Returns or sets whether all subpaths should be taken into sitemap. + /// + public bool IncludeSubPaths { get; internal set; } + + /// + /// Returns the internal routing path for the endpoint. + /// + public IRoute Route { get; internal set; } + + /// + /// Returns the attributes associated with the page. + /// + public IEnumerable Attributes => []; + + /// + /// Initializes a new instance of the class with the specified endpoint manager, parent type, context path, and path segment. + /// + public AssetContext() + { + } + + /// + /// Returns a string that represents the current object. + /// + /// A string that represents the current object. + public override string ToString() + { + return $"Asset: {EndpointId}"; + } + } +} diff --git a/src/WebExpress.WebCore/WebAsset/AssetManager.cs b/src/WebExpress.WebCore/WebAsset/AssetManager.cs new file mode 100644 index 0000000..44b7876 --- /dev/null +++ b/src/WebExpress.WebCore/WebAsset/AssetManager.cs @@ -0,0 +1,348 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using WebExpress.WebCore.Internationalization; +using WebExpress.WebCore.WebApplication; +using WebExpress.WebCore.WebAsset.Model; +using WebExpress.WebCore.WebComponent; +using WebExpress.WebCore.WebEndpoint; +using WebExpress.WebCore.WebMessage; +using WebExpress.WebCore.WebPlugin; +using WebExpress.WebCore.WebUri; + +namespace WebExpress.WebCore.WebAsset +{ + /// + /// The asset manager manages WebExpress elements, which can be called with a URI (Uniform Resource Identifier). + /// + public sealed class AssetManager : IAssetManager, ISystemComponent + { + private readonly IComponentHub _componentHub; + private readonly IHttpServerContext _httpServerContext; + private readonly AssetItemDictionary _itemDictionary = new(); + private readonly AssetEndpointDictionary _endpointDictionary = []; + + /// + /// An event that fires when an asset is added. + /// + public event EventHandler AddAsset; + + /// + /// An event that fires when an asset is removed. + /// + public event EventHandler RemoveAsset; + + /// + /// Returns all asset contexts. + /// + public IEnumerable Assets => _itemDictionary.All.Select(x => x.AssetContext); + + /// + /// Initializes a new instance of the class. + /// + /// The component hub. + /// The reference to the context of the host. + [SuppressMessage("CodeQuality", "IDE0051:Remove unused private members", Justification = "Used via Reflection.")] + private AssetManager(IComponentHub componentHub, IHttpServerContext httpServerContext) + { + _componentHub = componentHub; + + _componentHub.PluginManager.AddPlugin += OnAddPlugin; + _componentHub.PluginManager.RemovePlugin += OnRemovePlugin; + _componentHub.ApplicationManager.AddApplication += OnAddApplication; + _componentHub.ApplicationManager.RemoveApplication += OnRemoveApplication; + + var endpointtRegistration = new EndpointRegistration() + { + EndpointResolver = (type, applicationContext) => _endpointDictionary + .Where(x => x.Key == applicationContext) + .Select(x => x.Value) + .Where(x => x.Item2.GetType() == type) + .Select(x => x.Item1), + EndpointsResolver = () => _endpointDictionary + .Select(x => x.Value) + .Select(x => x.Item1), + HandleRequest = (request, endpointContext) => + { + var assetContext = endpointContext as IAssetContext; + var asset = _itemDictionary.All + .FirstOrDefault(x => request.Uri.ToString().ToLower().Replace('/', '.').EndsWith(x.AssetContext.EndpointId.ToString())); + + if (asset != null) + { + return asset.Instance.Process(request); + } + + return new ResponseNotFound(); + } + }; + + AddAsset += (sender, e) => endpointtRegistration.AddEndpoint?.Invoke(sender, e); + RemoveAsset += (sender, e) => endpointtRegistration.RemoveEndpoint?.Invoke(sender, e); + + _componentHub.EndpointManager.Register(endpointtRegistration); + + _httpServerContext = httpServerContext; + + _httpServerContext.Log.Debug + ( + I18N.Translate("webexpress.webcore:assetmanager.initialization") + ); + } + + /// + /// Discovers and binds resources to an application. + /// + /// The context of the plugin whose resources are to be associated. + private void Register(IPluginContext pluginContext) + { + if (_itemDictionary.ContainsPlugin(pluginContext)) + { + return; + } + + Register(pluginContext, _componentHub.ApplicationManager.GetApplications(pluginContext)); + } + + /// + /// Discovers and binds resources to an application. + /// + /// The context of the application whose resources are to be associated. + private void Register(IApplicationContext applicationContext) + { + foreach (var pluginContext in _componentHub.PluginManager.GetPlugins(applicationContext)) + { + if (_itemDictionary.ContainsApplication(pluginContext, applicationContext)) + { + continue; + } + + Register(pluginContext, [applicationContext]); + } + } + + /// + /// Registers resources for a given plugin and application context. + /// + /// The plugin context. + /// The application context (optional). + private void Register(IPluginContext pluginContext, IEnumerable applicationContexts) + { + var assembly = pluginContext?.Assembly; + var assemblName = assembly.GetName().Name; + var embeddedResources = assembly.GetManifestResourceNames(); + + foreach (var resource in embeddedResources) + { + if (resource.StartsWith(assemblName + ".Assets.", StringComparison.OrdinalIgnoreCase)) + { + var id = resource[(assemblName.Length + 8)..]; + + // assign the asset to existing applications + foreach (var applicationContext in applicationContexts) + { + var prefix = applicationContext.ContextPath + .Concat(new UriPathSegmentConstant("assets")) + .Concat + ( + applicationContext.PluginContext != pluginContext + ? pluginContext.PluginName.ToLower() + : "" + ); + + var assetContext = new AssetContext() + { + EndpointId = new ComponentId(id), + PluginContext = pluginContext, + ApplicationContext = applicationContext, + Route = prefix.Concat(new UriPathSegmentConstant($"{id}")), + IncludeSubPaths = false + }; + + var assetItem = new AssetItem(_componentHub.AssetManager) + { + AssetClass = typeof(Asset), + AssetContext = assetContext, + Instance = ComponentActivator.CreateInstance + ( + typeof(Asset), + assetContext, + _httpServerContext, + _componentHub, + resource + ) + }; + + if (_itemDictionary.AddAssetItem(pluginContext, applicationContext, assetItem)) + { + OnAddAsset(assetContext); + _httpServerContext?.Log.Debug( + I18N.Translate( + "webexpress.webcore:assetmanager.addresource", + id, + applicationContext.ApplicationId + ) + ); + } + } + } + } + } + + /// + /// Removes all resources associated with the specified plugin context. + /// + /// The context of the plugin that contains the resources to remove. + internal void Remove(IPluginContext pluginContext) + { + foreach (var assetContext in _itemDictionary.Remove(pluginContext)) + { + OnRemoveAsset(assetContext); + } + } + + /// + /// Removes all assets associated with the specified application context. + /// + /// The context of the application that contains the resources to remove. + internal void Remove(IApplicationContext applicationContext) + { + foreach (var assetContext in _itemDictionary.Remove(applicationContext)) + { + OnRemoveAsset(assetContext); + } + } + + /// + /// Returns an enumeration of all containing asset contexts of a plugin. + /// + /// A context of a plugin whose asset are to be registered. + /// An enumeration of asset contexts. + public IEnumerable GetAssets(IPluginContext pluginContext) + { + return _itemDictionary.GetAssets(pluginContext); + } + + /// + /// Returns an enumeration of asset contextes. + /// + /// The context of the application. + /// An enumeration of asset contextes. + public IEnumerable GetAssets(IApplicationContext applicationContext) + { + return _itemDictionary.GetAssets(applicationContext); + } + + /// + /// Raises the AddAsset event. + /// + /// The asset context. + private void OnAddAsset(IAssetContext assetContext) + { + AddAsset?.Invoke(this, assetContext); + } + + /// + /// Raises the RemoveAsset event. + /// + /// The asset context. + private void OnRemoveAsset(IAssetContext assetContext) + { + RemoveAsset?.Invoke(this, assetContext); + } + + /// + /// Raises the event when an plugin is added. + /// + /// The source of the event. + /// The context of the plugin being added. + private void OnAddPlugin(object sender, IPluginContext e) + { + Register(e); + } + + /// + /// Raises the event when a plugin is removed. + /// + /// The source of the event. + /// The context of the plugin being removed. + private void OnRemovePlugin(object sender, IPluginContext e) + { + Remove(e); + } + + /// + /// Raises the event when an application is added. + /// + /// The source of the event. + /// The context of the application being added. + private void OnAddApplication(object sender, IApplicationContext e) + { + Register(e); + + var assembly = typeof(AssetManager).Assembly; + var assemblyName = assembly.GetName().Name.ToLower(); + + var context = new AssetContext() + { + ApplicationContext = e, + PluginContext = new PluginContext() + { + PluginId = new ComponentId(assemblyName), + Assembly = assembly + }, + EndpointId = new ComponentId(assemblyName + ".asset"), + IncludeSubPaths = true, + Route = RouteEndpoint.Combine(e.ContextPath, "assets") + }; + + var asset = ComponentActivator.CreateInstance(typeof(Asset), context, _httpServerContext, _componentHub); + + _endpointDictionary.TryAdd(e, (context, asset)); + } + + /// + /// Raises the event when an application is removed. + /// + /// The source of the event. + /// The context of the application being removed. + private void OnRemoveApplication(object sender, IApplicationContext e) + { + Remove(e); + + _endpointDictionary.Remove(e); + } + + /// + /// Information about the component is collected and prepared for output in the log. + /// + private void Log() + { + //foreach (var resourcenItem in GetResorceItems(pluginContext)) + //{ + // output.Add + // ( + // string.Empty.PadRight(deep) + + // I18N.Translate + // ( + // "webexpress.webcore:resourcemanager.resource", + // resourcenItem?.ResourceContext?.EndpointId, + // string.Join(",", resourcenItem.ResourceContext?.ApplicationContext?.ApplicationId) + // ) + // ); + //} + } + + /// + /// Release of unmanaged resources reserved during use. + /// + public void Dispose() + { + _componentHub.PluginManager.AddPlugin -= OnAddPlugin; + _componentHub.PluginManager.RemovePlugin -= OnRemovePlugin; + _componentHub.ApplicationManager.AddApplication -= OnAddApplication; + _componentHub.ApplicationManager.RemoveApplication -= OnRemoveApplication; + } + } +} diff --git a/src/WebExpress.WebCore/WebAsset/IAsset.cs b/src/WebExpress.WebCore/WebAsset/IAsset.cs new file mode 100644 index 0000000..78d8e5c --- /dev/null +++ b/src/WebExpress.WebCore/WebAsset/IAsset.cs @@ -0,0 +1,18 @@ +using WebExpress.WebCore.WebEndpoint; +using WebExpress.WebCore.WebMessage; + +namespace WebExpress.WebCore.WebAsset +{ + /// + /// Defines the contract for a asset component. + /// + public interface IAsset : IEndpoint + { + /// + /// Processing of the resource. + /// + /// The request. + /// The response. + Response Process(Request request); + } +} diff --git a/src/WebExpress.WebCore/WebAsset/IAssetContext.cs b/src/WebExpress.WebCore/WebAsset/IAssetContext.cs new file mode 100644 index 0000000..93ec6a8 --- /dev/null +++ b/src/WebExpress.WebCore/WebAsset/IAssetContext.cs @@ -0,0 +1,12 @@ +using WebExpress.WebCore.WebEndpoint; + +namespace WebExpress.WebCore.WebAsset +{ + /// + /// Defines the context for a asset, providing access to various related contexts and properties. + /// + public interface IAssetContext : IEndpointContext + { + + } +} diff --git a/src/WebExpress.WebCore/WebAsset/IAssetManager.cs b/src/WebExpress.WebCore/WebAsset/IAssetManager.cs new file mode 100644 index 0000000..d97cca3 --- /dev/null +++ b/src/WebExpress.WebCore/WebAsset/IAssetManager.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using WebExpress.WebCore.WebApplication; +using WebExpress.WebCore.WebComponent; +using WebExpress.WebCore.WebPlugin; + +namespace WebExpress.WebCore.WebAsset +{ + /// + /// The asset manager manages resources elements, which can be called with a URI (Uniform Resource Identifier). + /// + public interface IAssetManager : IComponentManager + { + /// + /// An event that fires when an asset is added. + /// + event EventHandler AddAsset; + + /// + /// An event that fires when an asset is removed. + /// + event EventHandler RemoveAsset; + + /// + /// Returns all asset contexts. + /// + IEnumerable Assets { get; } + + /// + /// Returns an enumeration of all containing asset contexts of a plugin. + /// + /// A context of a plugin whose resources are to be registered. + /// An enumeration of resource contexts. + IEnumerable GetAssets(IPluginContext pluginContext); + + /// + /// Returns an enumeration of asset contextes. + /// + /// The context of the application. + /// An enumeration of asset contextes. + IEnumerable GetAssets(IApplicationContext applicationContext); + } +} diff --git a/src/WebExpress.WebCore/WebAsset/Model/AssetEndpointDictionary.cs b/src/WebExpress.WebCore/WebAsset/Model/AssetEndpointDictionary.cs new file mode 100644 index 0000000..5e9f39c --- /dev/null +++ b/src/WebExpress.WebCore/WebAsset/Model/AssetEndpointDictionary.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; +using WebExpress.WebCore.WebApplication; + +namespace WebExpress.WebCore.WebAsset.Model +{ + /// + /// Represents a dictionary that maps plugin contexts to application contexts and assets. + /// key = plugin context + /// value = { key = application context, value = asset } + /// + internal class AssetEndpointDictionary : Dictionary + { + + } +} diff --git a/src/WebExpress.WebCore/WebAsset/Model/AssetItem.cs b/src/WebExpress.WebCore/WebAsset/Model/AssetItem.cs new file mode 100644 index 0000000..b0a04dd --- /dev/null +++ b/src/WebExpress.WebCore/WebAsset/Model/AssetItem.cs @@ -0,0 +1,53 @@ +using System; + +namespace WebExpress.WebCore.WebAsset.Model +{ + /// + /// A assat element that contains meta information about a asset. + /// + internal class AssetItem : IDisposable + { + private readonly IAssetManager _assetManager; + + /// + /// Returns or sets the type of asset. + /// + public Type AssetClass { get; set; } + + /// + /// Returns or sets the instance of the asset, if the asset is cached, otherwise null. + /// + public IAsset Instance { get; set; } + + /// + /// Returns the asset context. + /// + public IAssetContext AssetContext { get; internal set; } + + /// + /// Initializes a new instance of the class. + /// + /// The asset manager. + internal AssetItem(IAssetManager assetManager) + { + _assetManager = assetManager; + } + + /// + /// Performs application-specific tasks related to sharing, returning, or resetting unmanaged resources. + /// + public void Dispose() + { + + } + + /// + /// Convert the asset element to a string. + /// + /// The asset element in its string representation. + public override string ToString() + { + return $"Asset: '{AssetContext?.EndpointId}'"; + } + } +} diff --git a/src/WebExpress.WebCore/WebAsset/Model/AssetItemDictionary.cs b/src/WebExpress.WebCore/WebAsset/Model/AssetItemDictionary.cs new file mode 100644 index 0000000..4924daa --- /dev/null +++ b/src/WebExpress.WebCore/WebAsset/Model/AssetItemDictionary.cs @@ -0,0 +1,203 @@ +using System.Collections.Generic; +using System.Linq; +using WebExpress.WebCore.WebApplication; +using WebExpress.WebCore.WebPlugin; + +namespace WebExpress.WebCore.WebAsset.Model +{ + /// + /// Represents a dictionary that maps plugin contexts to application contexts and asset items. + /// key = plugin context + /// value = { key = resource type, value = ressource item } + /// + internal class AssetItemDictionary + { + private readonly Dictionary>> _dictionary = []; + + /// + /// Returns all asset items. + /// + public IEnumerable All => _dictionary.Values + .SelectMany(x => x.Values) + .SelectMany(x => x); + + /// + /// Adds a asset item to the dictionary. + /// + /// The plugin context. + /// The application context. + /// The resource item. + /// True if the resource item was added successfully, false if an element with the same status code already exists. + public bool AddAssetItem(IPluginContext pluginContext, IApplicationContext applicationContext, AssetItem assetItem) + { + var type = assetItem.AssetClass; + + if (!typeof(IAsset).IsAssignableFrom(type)) + { + return false; + } + + if (!_dictionary.ContainsKey(pluginContext)) + { + _dictionary[pluginContext] = []; + } + + var appContextDict = _dictionary[pluginContext]; + + if (!appContextDict.TryGetValue(applicationContext, out List value)) + { + value = ([]); + appContextDict[applicationContext] = value; + } + + var assetList = value; + + assetList.RemoveAll(x => x.AssetContext?.EndpointId == assetItem.AssetContext.EndpointId); + assetList.Add(assetItem); + + return true; + } + + /// + /// Removes all resources associated with the specified plugin context. + /// + /// The context of the plugin that contains the resources to remove. + /// An enumeration of asset contexts that were removed. + public IEnumerable Remove(IPluginContext pluginContext) + { + if (pluginContext == null) + { + return []; + } + + // the plugin has not been registered in the manager + if (_dictionary.TryGetValue(pluginContext, out var value)) + { + var items = value.Values + .SelectMany(x => x) + .ToList(); + + foreach (var assetItem in items) + { + assetItem.Dispose(); + } + + _dictionary.Remove(pluginContext); + + return items.Select(x => x.AssetContext); + } + + return []; + } + + /// + /// Removes all assets associated with the specified application context. + /// + /// The context of the application that contains the resources to remove. + /// An enumeration of asset contexts that were removed. + internal IEnumerable Remove(IApplicationContext applicationContext) + { + if (applicationContext == null) + { + return []; + } + + var removedAssets = new List(); + + foreach (var pluginDict in _dictionary.Values) + { + foreach (var assetList in pluginDict.Where(x => x.Key == applicationContext).Select(x => x.Value)) + { + foreach (var assetItem in assetList) + { + removedAssets.Add(assetItem.AssetContext); + assetItem.Dispose(); + } + } + + pluginDict.Remove(applicationContext); + } + + return removedAssets; + } + + /// + /// Checks if the dictionary contains the specified plugin context. + /// + /// The plugin context to check. + /// True if the plugin context exists in the dictionary, otherwise false. + public bool ContainsPlugin(IPluginContext pluginContext) + { + return _dictionary.ContainsKey(pluginContext); + } + + /// + /// Checks if the dictionary contains the specified application context. + /// + /// The plugin context to check. + /// The application context to check. + /// True if the application context exists in the dictionary, otherwise false. + public bool ContainsApplication(IPluginContext pluginContext, IApplicationContext applicationContext) + { + if (_dictionary.TryGetValue(pluginContext, out var appDict) && appDict.ContainsKey(applicationContext)) + { + return true; + } + + return false; + } + + /// + /// Returns the asset items from the dictionary. + /// + /// The application context. + /// An IEnumerable of asset items + public IEnumerable GetAssetItems(IApplicationContext applicationContext) + { + if (_dictionary.ContainsKey(applicationContext?.PluginContext)) + { + var appContextDict = _dictionary[applicationContext?.PluginContext]; + + if (appContextDict.TryGetValue(applicationContext, out List value)) + { + var assetList = value; + + return assetList; + } + } + + return []; + } + + /// + /// Returns an enumeration of all containing asset contexts of a plugin. + /// + /// A context of a plugin whose asset are to be registered. + /// An enumeration of asset contexts. + public IEnumerable GetAssets(IPluginContext pluginContext) + { + if (_dictionary.TryGetValue(pluginContext, out var pluginResources)) + { + return pluginResources + .SelectMany(x => x.Value) + .Select(x => x.AssetContext); + } + + return []; + } + + /// + /// Returns an enumeration of asset contextes. + /// + /// The context of the application. + /// An enumeration of asset contextes. + public IEnumerable GetAssets(IApplicationContext applicationContext) + { + return _dictionary.Values + .SelectMany(x => x.Values) + .SelectMany(x => x) + .Where(x => x.AssetContext.ApplicationContext.Equals(applicationContext)) + .Select(x => x.AssetContext); + } + } +} diff --git a/src/WebExpress.WebCore/WebAttribute/ApplicationAttribute.cs b/src/WebExpress.WebCore/WebAttribute/ApplicationAttribute.cs index 836a363..adb5ee8 100644 --- a/src/WebExpress.WebCore/WebAttribute/ApplicationAttribute.cs +++ b/src/WebExpress.WebCore/WebAttribute/ApplicationAttribute.cs @@ -3,28 +3,12 @@ namespace WebExpress.WebCore.WebAttribute { - /// - /// Application assignment attribute of the application ID. - /// - [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] - public class ApplicationAttribute : Attribute, IModuleAttribute - { - /// - /// Constructor - /// - /// A specific ApplicationId, regular expression, or * for any application. - public ApplicationAttribute(string applicationId) - { - - } - } - /// /// An application expression attribute, which is determined by the type. /// - /// The type of the application. + /// The type of the application. [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] - public class ApplicationAttribute : Attribute, IModuleAttribute where T : class, IApplication + public class ApplicationAttribute : Attribute, IPluginAttribute where TApplication : class, IApplication { } diff --git a/src/WebExpress.WebCore/WebAttribute/AssetPathAttribute.cs b/src/WebExpress.WebCore/WebAttribute/AssetPathAttribute.cs index b535463..188e32f 100644 --- a/src/WebExpress.WebCore/WebAttribute/AssetPathAttribute.cs +++ b/src/WebExpress.WebCore/WebAttribute/AssetPathAttribute.cs @@ -1,9 +1,16 @@ -namespace WebExpress.WebCore.WebAttribute +using System; + +namespace WebExpress.WebCore.WebAttribute { - public class AssetPathAttribute : System.Attribute, IApplicationAttribute, IModuleAttribute + + /// + /// Attribute to specify the path for assets in the application. + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] + public class AssetPathAttribute : Attribute, IApplicationAttribute { /// - /// Constructor + /// Initializes a new instance of the class. /// /// The path for assets. public AssetPathAttribute(string assetPath) diff --git a/src/WebExpress.WebCore/WebAttribute/CacheAttribute.cs b/src/WebExpress.WebCore/WebAttribute/CacheAttribute.cs index 968e73c..e90b6ab 100644 --- a/src/WebExpress.WebCore/WebAttribute/CacheAttribute.cs +++ b/src/WebExpress.WebCore/WebAttribute/CacheAttribute.cs @@ -5,11 +5,11 @@ namespace WebExpress.WebCore.WebAttribute /// /// Indicates that a page or component can be reused /// - [AttributeUsage(AttributeTargets.Class)] - public class CacheAttribute : System.Attribute, IResourceAttribute + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] + public class CacheAttribute : Attribute, IEndpointAttribute { /// - /// Constructor + /// Initializes a new instance of the class. /// public CacheAttribute() { diff --git a/src/WebExpress.WebCore/WebAttribute/ConditionAttribute.cs b/src/WebExpress.WebCore/WebAttribute/ConditionAttribute.cs index 4b32bb1..245d8ae 100644 --- a/src/WebExpress.WebCore/WebAttribute/ConditionAttribute.cs +++ b/src/WebExpress.WebCore/WebAttribute/ConditionAttribute.cs @@ -6,11 +6,11 @@ namespace WebExpress.WebCore.WebAttribute /// /// Activation of options (e.g. WebEx.WebApp.Setting.SystemInformation for displaying system information). /// - [AttributeUsage(AttributeTargets.Class)] - public class ConditionAttribute : Attribute, IResourceAttribute where T : class, ICondition + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] + public class ConditionAttribute : Attribute, IEndpointAttribute where T : class, ICondition { /// - /// Constructor + /// Initializes a new instance of the class. /// public ConditionAttribute() { diff --git a/src/WebExpress.WebCore/WebAttribute/ContextPathAttribute.cs b/src/WebExpress.WebCore/WebAttribute/ContextPathAttribute.cs index 0fa43f5..d8554f2 100644 --- a/src/WebExpress.WebCore/WebAttribute/ContextPathAttribute.cs +++ b/src/WebExpress.WebCore/WebAttribute/ContextPathAttribute.cs @@ -2,11 +2,14 @@ namespace WebExpress.WebCore.WebAttribute { + /// + /// Attribute to define the context path for an application or endpoint. + /// [AttributeUsage(AttributeTargets.All, AllowMultiple = false)] - public class ContextPathAttribute : Attribute, IApplicationAttribute, IModuleAttribute, IResourceAttribute + public class ContextPathAttribute : Attribute, IApplicationAttribute, IEndpointAttribute { /// - /// Constructor + /// Initializes a new instance of the class. /// /// The context path. public ContextPathAttribute(string contetxPath) diff --git a/src/WebExpress.WebCore/WebAttribute/DataPathAttribute.cs b/src/WebExpress.WebCore/WebAttribute/DataPathAttribute.cs index 5cfe84c..40b5754 100644 --- a/src/WebExpress.WebCore/WebAttribute/DataPathAttribute.cs +++ b/src/WebExpress.WebCore/WebAttribute/DataPathAttribute.cs @@ -1,9 +1,15 @@ -namespace WebExpress.WebCore.WebAttribute +using System; + +namespace WebExpress.WebCore.WebAttribute { - public class DataPathAttribute : System.Attribute, IApplicationAttribute, IModuleAttribute + /// + /// Attribute to specify the data path for an application. + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] + public class DataPathAttribute : System.Attribute, IApplicationAttribute { /// - /// Constructor + /// Initializes a new instance of the class. /// /// The path for the data. public DataPathAttribute(string dataPath) diff --git a/src/WebExpress.WebCore/WebAttribute/DefaultAttribute.cs b/src/WebExpress.WebCore/WebAttribute/DefaultAttribute.cs index 7b45a50..d7fa97f 100644 --- a/src/WebExpress.WebCore/WebAttribute/DefaultAttribute.cs +++ b/src/WebExpress.WebCore/WebAttribute/DefaultAttribute.cs @@ -5,11 +5,11 @@ namespace WebExpress.WebCore.WebAttribute /// /// Indicates that a status page ist default /// - [AttributeUsage(AttributeTargets.Class)] + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] public class DefaultAttribute : Attribute, IStatusPageAttribute { /// - /// Constructor + /// Initializes a new instance of the class. /// public DefaultAttribute() { diff --git a/src/WebExpress.WebCore/WebAttribute/DependencyAttribute.cs b/src/WebExpress.WebCore/WebAttribute/DependencyAttribute.cs index d1519d9..1668cc2 100644 --- a/src/WebExpress.WebCore/WebAttribute/DependencyAttribute.cs +++ b/src/WebExpress.WebCore/WebAttribute/DependencyAttribute.cs @@ -6,10 +6,10 @@ namespace WebExpress.WebCore.WebAttribute /// Marks a plugin as dependent on another plugin. /// [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] - public class DependencyAttribute : System.Attribute, IPluginAttribute + public class DependencyAttribute : Attribute, IPluginAttribute { /// - /// Constructor + /// Initializes a new instance of the class. /// /// The Id of the plugin to which there is a dependency. public DependencyAttribute(string dependency) diff --git a/src/WebExpress.WebCore/WebAttribute/DescriptionAttribute.cs b/src/WebExpress.WebCore/WebAttribute/DescriptionAttribute.cs index 414e31e..750f2a4 100644 --- a/src/WebExpress.WebCore/WebAttribute/DescriptionAttribute.cs +++ b/src/WebExpress.WebCore/WebAttribute/DescriptionAttribute.cs @@ -1,9 +1,16 @@ -namespace WebExpress.WebCore.WebAttribute +using System; + +namespace WebExpress.WebCore.WebAttribute { - public class DescriptionAttribute : System.Attribute, IPluginAttribute, IApplicationAttribute, IModuleAttribute + /// + /// Attribute to provide a description for a class. + /// Implements , , and . + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] + public class DescriptionAttribute : Attribute, IPluginAttribute, IApplicationAttribute, ISettingCategoryAttribute, ISettingGroupAttribute, IThemeAttribute { /// - /// Constructor + /// Initializes a new instance of the class. /// /// The description. public DescriptionAttribute(string description) diff --git a/src/WebExpress.WebCore/WebAttribute/EventAttribute.cs b/src/WebExpress.WebCore/WebAttribute/EventAttribute.cs new file mode 100644 index 0000000..0e7991a --- /dev/null +++ b/src/WebExpress.WebCore/WebAttribute/EventAttribute.cs @@ -0,0 +1,21 @@ +using System; +using WebExpress.WebCore.WebEvent; + +namespace WebExpress.WebCore.WebAttribute +{ + /// + /// Specifying a event. + /// + /// The type of the event. + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] + public class EventAttribute : Attribute, IEventAttribute where T : class, IEvent + { + /// + /// Initializes a new instance of the class. + /// + public EventAttribute() + { + + } + } +} diff --git a/src/WebExpress.WebCore/WebAttribute/IResourceAttribute.cs b/src/WebExpress.WebCore/WebAttribute/IEndpointAttribute.cs similarity index 79% rename from src/WebExpress.WebCore/WebAttribute/IResourceAttribute.cs rename to src/WebExpress.WebCore/WebAttribute/IEndpointAttribute.cs index e845ac5..7ce2128 100644 --- a/src/WebExpress.WebCore/WebAttribute/IResourceAttribute.cs +++ b/src/WebExpress.WebCore/WebAttribute/IEndpointAttribute.cs @@ -3,7 +3,7 @@ /// /// Interface of a resource assignment attribute. /// - public interface IResourceAttribute + public interface IEndpointAttribute { } } diff --git a/src/WebExpress.WebCore/WebAttribute/IModuleAttribute.cs b/src/WebExpress.WebCore/WebAttribute/IEventAttribute.cs similarity index 52% rename from src/WebExpress.WebCore/WebAttribute/IModuleAttribute.cs rename to src/WebExpress.WebCore/WebAttribute/IEventAttribute.cs index 4ecba85..451ca33 100644 --- a/src/WebExpress.WebCore/WebAttribute/IModuleAttribute.cs +++ b/src/WebExpress.WebCore/WebAttribute/IEventAttribute.cs @@ -1,9 +1,9 @@ namespace WebExpress.WebCore.WebAttribute { /// - /// Interface of a module assignment attribute. + /// Interface of a event assignment attribute. /// - public interface IModuleAttribute + public interface IEventAttribute { } } diff --git a/src/WebExpress.WebCore/WebAttribute/IFragmentAttribute.cs b/src/WebExpress.WebCore/WebAttribute/IFragmentAttribute.cs new file mode 100644 index 0000000..b8f1860 --- /dev/null +++ b/src/WebExpress.WebCore/WebAttribute/IFragmentAttribute.cs @@ -0,0 +1,9 @@ +namespace WebExpress.WebCore.WebAttribute +{ + /// + /// Marks a class as a fragment component. + /// + public interface IFragmentAttribute + { + } +} diff --git a/src/WebExpress.WebCore/WebAttribute/IPageAttribute.cs b/src/WebExpress.WebCore/WebAttribute/IPageAttribute.cs new file mode 100644 index 0000000..57ef491 --- /dev/null +++ b/src/WebExpress.WebCore/WebAttribute/IPageAttribute.cs @@ -0,0 +1,9 @@ +namespace WebExpress.WebCore.WebAttribute +{ + /// + /// Interface of a page assignment attribute. + /// + public interface IPageAttribute : IEndpointAttribute + { + } +} diff --git a/src/WebExpress.WebCore/WebAttribute/IPermissionAttribute.cs b/src/WebExpress.WebCore/WebAttribute/IPermissionAttribute.cs new file mode 100644 index 0000000..6dc2788 --- /dev/null +++ b/src/WebExpress.WebCore/WebAttribute/IPermissionAttribute.cs @@ -0,0 +1,9 @@ +namespace WebExpress.WebCore.WebAttribute +{ + /// + /// Interface of a permission assignment attribute. + /// + public interface IPermissionAttribute + { + } +} diff --git a/src/WebExpress.WebCore/WebAttribute/IRestApiAttribute.cs b/src/WebExpress.WebCore/WebAttribute/IRestApiAttribute.cs new file mode 100644 index 0000000..976ecab --- /dev/null +++ b/src/WebExpress.WebCore/WebAttribute/IRestApiAttribute.cs @@ -0,0 +1,9 @@ +namespace WebExpress.WebCore.WebAttribute +{ + /// + /// Interface of a rest api assignment attribute. + /// + public interface IRestApiAttribute : IEndpointAttribute + { + } +} diff --git a/src/WebExpress.WebCore/WebAttribute/IRoleAttribute.cs b/src/WebExpress.WebCore/WebAttribute/IRoleAttribute.cs new file mode 100644 index 0000000..f868701 --- /dev/null +++ b/src/WebExpress.WebCore/WebAttribute/IRoleAttribute.cs @@ -0,0 +1,9 @@ +namespace WebExpress.WebCore.WebAttribute +{ + /// + /// Interface of a role assignment attribute. + /// + public interface IRoleAttribute + { + } +} diff --git a/src/WebExpress.WebCore/WebAttribute/ISegmentAttribute.cs b/src/WebExpress.WebCore/WebAttribute/ISegmentAttribute.cs index 9022a55..10ac119 100644 --- a/src/WebExpress.WebCore/WebAttribute/ISegmentAttribute.cs +++ b/src/WebExpress.WebCore/WebAttribute/ISegmentAttribute.cs @@ -2,6 +2,9 @@ namespace WebExpress.WebCore.WebAttribute { + /// + /// Interface for converting an object to a URI path segment. + /// public interface ISegmentAttribute { /// diff --git a/src/WebExpress.WebCore/WebAttribute/ISettingCategoryAttribute.cs b/src/WebExpress.WebCore/WebAttribute/ISettingCategoryAttribute.cs new file mode 100644 index 0000000..618eb8c --- /dev/null +++ b/src/WebExpress.WebCore/WebAttribute/ISettingCategoryAttribute.cs @@ -0,0 +1,9 @@ +namespace WebExpress.WebCore.WebAttribute +{ + /// + /// Interface of a setting category assignment attribute. + /// + public interface ISettingCategoryAttribute + { + } +} diff --git a/src/WebExpress.WebCore/WebAttribute/ISettingGroupAttribute.cs b/src/WebExpress.WebCore/WebAttribute/ISettingGroupAttribute.cs new file mode 100644 index 0000000..695cd6f --- /dev/null +++ b/src/WebExpress.WebCore/WebAttribute/ISettingGroupAttribute.cs @@ -0,0 +1,9 @@ +namespace WebExpress.WebCore.WebAttribute +{ + /// + /// Interface of a setting group assignment attribute. + /// + public interface ISettingGroupAttribute + { + } +} diff --git a/src/WebExpress.WebCore/WebAttribute/ISettingPageAttribute.cs b/src/WebExpress.WebCore/WebAttribute/ISettingPageAttribute.cs new file mode 100644 index 0000000..2ad2955 --- /dev/null +++ b/src/WebExpress.WebCore/WebAttribute/ISettingPageAttribute.cs @@ -0,0 +1,9 @@ +namespace WebExpress.WebCore.WebAttribute +{ + /// + /// Interface of a setting page assignment attribute. + /// + public interface ISettingPageAttribute : IEndpointAttribute + { + } +} diff --git a/src/WebExpress.WebCore/WebAttribute/IThemeAttribute.cs b/src/WebExpress.WebCore/WebAttribute/IThemeAttribute.cs new file mode 100644 index 0000000..f34e542 --- /dev/null +++ b/src/WebExpress.WebCore/WebAttribute/IThemeAttribute.cs @@ -0,0 +1,9 @@ +namespace WebExpress.WebCore.WebAttribute +{ + /// + /// Interface of a theme assignment attribute. + /// + public interface IThemeAttribute + { + } +} diff --git a/src/WebExpress.WebCore/WebAttribute/IconAttribute.cs b/src/WebExpress.WebCore/WebAttribute/IconAttribute.cs index 6257c6f..7f8643f 100644 --- a/src/WebExpress.WebCore/WebAttribute/IconAttribute.cs +++ b/src/WebExpress.WebCore/WebAttribute/IconAttribute.cs @@ -1,9 +1,15 @@ -namespace WebExpress.WebCore.WebAttribute +using System; + +namespace WebExpress.WebCore.WebAttribute { - public class IconAttribute : System.Attribute, IPluginAttribute, IApplicationAttribute, IModuleAttribute + /// + /// Attribute to specify an icon for a plugin, application, or status page. + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] + public class IconAttribute : Attribute, IPluginAttribute, IApplicationAttribute, IStatusPageAttribute { /// - /// Constructor + /// Initializes a new instance of the class. /// /// The icon. public IconAttribute(string icon) diff --git a/src/WebExpress.WebCore/WebAttribute/IdAttribute.cs b/src/WebExpress.WebCore/WebAttribute/IdAttribute.cs new file mode 100644 index 0000000..2a0cb44 --- /dev/null +++ b/src/WebExpress.WebCore/WebAttribute/IdAttribute.cs @@ -0,0 +1,18 @@ +namespace WebExpress.WebCore.WebAttribute +{ + /// + /// The unique identification key. + /// + [System.AttributeUsage(System.AttributeTargets.Class)] + public class IdAttribute : System.Attribute, IPluginAttribute, IApplicationAttribute, IEndpointAttribute + { + /// + /// Initializes a new instance of the class. + /// + /// The id. + public IdAttribute(string id) + { + + } + } +} diff --git a/src/WebExpress.WebCore/WebAttribute/ImageAttribute.cs b/src/WebExpress.WebCore/WebAttribute/ImageAttribute.cs new file mode 100644 index 0000000..0ed9d7a --- /dev/null +++ b/src/WebExpress.WebCore/WebAttribute/ImageAttribute.cs @@ -0,0 +1,20 @@ +using System; + +namespace WebExpress.WebCore.WebAttribute +{ + /// + /// Attribute to specify an image for a theme. + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] + public class ImageAttribute : Attribute, IThemeAttribute + { + /// + /// Initializes a new instance of the class. + /// + /// The image. + public ImageAttribute(string image) + { + + } + } +} diff --git a/src/WebExpress.WebCore/WebAttribute/IncludeSubPathsAttribute.cs b/src/WebExpress.WebCore/WebAttribute/IncludeSubPathsAttribute.cs index 6e596ec..f14ea97 100644 --- a/src/WebExpress.WebCore/WebAttribute/IncludeSubPathsAttribute.cs +++ b/src/WebExpress.WebCore/WebAttribute/IncludeSubPathsAttribute.cs @@ -3,10 +3,10 @@ /// /// Determines whether all resources below the specified path (including segment) are also processed. /// - public class IncludeSubPathsAttribute : System.Attribute, IResourceAttribute + public class IncludeSubPathsAttribute : System.Attribute, IEndpointAttribute { /// - /// Constructor + /// Initializes a new instance of the class. /// /// All subpaths are included. public IncludeSubPathsAttribute(bool includeSubPaths) diff --git a/src/WebExpress.WebCore/WebAttribute/JobAttribute.cs b/src/WebExpress.WebCore/WebAttribute/JobAttribute.cs index a1c431e..e6fbe3e 100644 --- a/src/WebExpress.WebCore/WebAttribute/JobAttribute.cs +++ b/src/WebExpress.WebCore/WebAttribute/JobAttribute.cs @@ -1,9 +1,13 @@ namespace WebExpress.WebCore.WebAttribute { + /// + /// Represents an attribute to schedule jobs based on specified time intervals. + /// + [System.AttributeUsage(System.AttributeTargets.Class)] public class JobAttribute : System.Attribute { /// - /// Constructor + /// Initializes a new instance of the class. /// /// The minute 0-59 or * for any. Comma-separated values or ranges (-) are also possible. /// The hour 0-23 or * for arbitrary. Comma-separated values or ranges (-) are also possible. diff --git a/src/WebExpress.WebCore/WebAttribute/MethodAttribute.cs b/src/WebExpress.WebCore/WebAttribute/MethodAttribute.cs new file mode 100644 index 0000000..c78a190 --- /dev/null +++ b/src/WebExpress.WebCore/WebAttribute/MethodAttribute.cs @@ -0,0 +1,20 @@ +using System; +using WebExpress.WebCore.WebRestApi; + +namespace WebExpress.WebCore.WebAttribute +{ + /// + /// The range in which the attribute is valid. + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] + public class MethodAttribute : Attribute, IEndpointAttribute + { + /// + /// Initializes a new instance of the class. + /// + public MethodAttribute(CrudMethod crudMethod) + { + + } + } +} diff --git a/src/WebExpress.WebCore/WebAttribute/ModuleAttribute.cs b/src/WebExpress.WebCore/WebAttribute/ModuleAttribute.cs deleted file mode 100644 index 6face4f..0000000 --- a/src/WebExpress.WebCore/WebAttribute/ModuleAttribute.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; -using WebExpress.WebCore.WebModule; - -namespace WebExpress.WebCore.WebAttribute -{ - /// - /// Specifying a module. - /// - /// The type of the module. - [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] - public class ModuleAttribute : Attribute, IResourceAttribute, IModuleAttribute where T : class, IModule - { - /// - /// Constructor - /// - public ModuleAttribute() - { - - } - } -} diff --git a/src/WebExpress.WebCore/WebAttribute/NameAttribute.cs b/src/WebExpress.WebCore/WebAttribute/NameAttribute.cs index 03e02a6..e9d30b4 100644 --- a/src/WebExpress.WebCore/WebAttribute/NameAttribute.cs +++ b/src/WebExpress.WebCore/WebAttribute/NameAttribute.cs @@ -1,11 +1,18 @@ -namespace WebExpress.WebCore.WebAttribute +using System; + +namespace WebExpress.WebCore.WebAttribute { - public class NameAttribute : System.Attribute, IPluginAttribute, IApplicationAttribute, IModuleAttribute + /// + /// Attribute to assign a name to a class. + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] + public class NameAttribute : Attribute, IPluginAttribute, IApplicationAttribute, ISettingCategoryAttribute, ISettingGroupAttribute, IThemeAttribute { /// - /// Constructor + /// Initializes a new instance of the class. /// /// The name. + public NameAttribute(string name) { diff --git a/src/WebExpress.WebCore/WebAttribute/OptionAttribute.cs b/src/WebExpress.WebCore/WebAttribute/OptionAttribute.cs deleted file mode 100644 index 5fa300f..0000000 --- a/src/WebExpress.WebCore/WebAttribute/OptionAttribute.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System; -using WebExpress.WebCore.WebModule; -using WebExpress.WebCore.WebResource; - -namespace WebExpress.WebCore.WebAttribute -{ - /// - /// Activation of options (e.g. 'webexpress.webapp.settinglog' or 'webexpress.webapp.*'). - /// - [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] - public class OptionAttribute : Attribute, IApplicationAttribute - { - /// - /// Constructor - /// - /// The option to activate. - public OptionAttribute(string option) - { - - } - } - - /// - /// Activation of options (e.g. 'webexpress.webapp.settinglog' or 'webexpress.webapp.*'). - /// - /// The module or resource class. - [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] - public class WebExOptionAttribute : Attribute, IApplicationAttribute where M : class, IModule - { - - } - - /// - /// Activation of options (e.g. 'webexpress.webapp.settinglog' or 'webexpress.webapp.*'). - /// The module or resource class. - /// The resource or resource class. - /// - [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] - public class WebExOptionAttribute : Attribute, IApplicationAttribute where M : class, IModule where R : class, IResource - { - } -} diff --git a/src/WebExpress.WebCore/WebAttribute/OptionalAttribute.cs b/src/WebExpress.WebCore/WebAttribute/OptionalAttribute.cs deleted file mode 100644 index 792b663..0000000 --- a/src/WebExpress.WebCore/WebAttribute/OptionalAttribute.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; - -namespace WebExpress.WebCore.WebAttribute -{ - /// - /// Marks a ressorce as optional. This becomes active when the option is specified in the application. - /// - [AttributeUsage(AttributeTargets.Class)] - public class OptionalAttribute : Attribute, IResourceAttribute - { - /// - /// Constructor - /// - public OptionalAttribute() - { - - } - } -} diff --git a/src/WebExpress.WebCore/WebAttribute/OrderAttribute.cs b/src/WebExpress.WebCore/WebAttribute/OrderAttribute.cs new file mode 100644 index 0000000..2a548c8 --- /dev/null +++ b/src/WebExpress.WebCore/WebAttribute/OrderAttribute.cs @@ -0,0 +1,20 @@ +using System; + +namespace WebExpress.WebCore.WebAttribute +{ + /// + /// Attribute used to identify a class as a plugin component. + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] + public class OrderAttribute : System.Attribute, IFragmentAttribute + { + /// + /// Initializes a new instance of the class. + /// + /// The order within the section. + public OrderAttribute(int order) + { + } + + } +} diff --git a/src/WebExpress.WebCore/WebAttribute/ParentAttribute.cs b/src/WebExpress.WebCore/WebAttribute/ParentAttribute.cs deleted file mode 100644 index ae0a043..0000000 --- a/src/WebExpress.WebCore/WebAttribute/ParentAttribute.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; -using WebExpress.WebCore.WebResource; - -namespace WebExpress.WebCore.WebAttribute -{ - [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] - public class ParentAttribute : Attribute, IResourceAttribute where T : class, IResource - { - /// - /// Constructor - /// - public ParentAttribute() - { - - } - } -} diff --git a/src/WebExpress.WebCore/WebAttribute/PermissionAttribute.cs b/src/WebExpress.WebCore/WebAttribute/PermissionAttribute.cs new file mode 100644 index 0000000..704de81 --- /dev/null +++ b/src/WebExpress.WebCore/WebAttribute/PermissionAttribute.cs @@ -0,0 +1,20 @@ +using System; +using WebExpress.WebCore.WebIdentity; + +namespace WebExpress.WebCore.WebAttribute +{ + /// + /// Connects roles with permissions. + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] + public class PermissionAttribute : Attribute, IPermissionAttribute where T : class, IIdentityPermission + { + /// + /// Initializes a new instance of the class. + /// + public PermissionAttribute() + { + + } + } +} diff --git a/src/WebExpress.WebCore/WebAttribute/RoleAttribute.cs b/src/WebExpress.WebCore/WebAttribute/RoleAttribute.cs new file mode 100644 index 0000000..8970557 --- /dev/null +++ b/src/WebExpress.WebCore/WebAttribute/RoleAttribute.cs @@ -0,0 +1,20 @@ +using System; +using WebExpress.WebCore.WebIdentity; + +namespace WebExpress.WebCore.WebAttribute +{ + /// + /// Connects roles with permissions. + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] + public class RoleAttribute : Attribute, IRoleAttribute where T : class, IIdentityRole + { + /// + /// Initializes a new instance of the class. + /// + public RoleAttribute() + { + + } + } +} diff --git a/src/WebExpress.WebCore/WebAttribute/ScopeAttribute.cs b/src/WebExpress.WebCore/WebAttribute/ScopeAttribute.cs index fdb024e..a2dd838 100644 --- a/src/WebExpress.WebCore/WebAttribute/ScopeAttribute.cs +++ b/src/WebExpress.WebCore/WebAttribute/ScopeAttribute.cs @@ -6,11 +6,11 @@ namespace WebExpress.WebCore.WebAttribute /// /// The range in which the component is valid. /// - [AttributeUsage(AttributeTargets.All, AllowMultiple = true)] - public class ScopeAttribute : Attribute, IResourceAttribute where T : class, IScope + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] + public class ScopeAttribute : Attribute, IPageAttribute, ISettingPageAttribute where T : class, IScope { /// - /// Constructor + /// Initializes a new instance of the class. /// public ScopeAttribute() { diff --git a/src/WebExpress.WebCore/WebAttribute/SectionAttribute.cs b/src/WebExpress.WebCore/WebAttribute/SectionAttribute.cs new file mode 100644 index 0000000..5445fd6 --- /dev/null +++ b/src/WebExpress.WebCore/WebAttribute/SectionAttribute.cs @@ -0,0 +1,19 @@ +using System; +using WebExpress.WebCore.WebSection; + +namespace WebExpress.WebCore.WebAttribute +{ + /// + /// Attribute to identify a section. + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] + public class SectionAttribute : Attribute, IFragmentAttribute, ISettingCategoryAttribute, ISettingGroupAttribute where T : class, ISection + { + /// + /// Initializes a new instance of the class. + /// + public SectionAttribute() + { + } + } +} diff --git a/src/WebExpress.WebCore/WebAttribute/SegmentAttribute.cs b/src/WebExpress.WebCore/WebAttribute/SegmentAttribute.cs index 8c7954d..6c4884d 100644 --- a/src/WebExpress.WebCore/WebAttribute/SegmentAttribute.cs +++ b/src/WebExpress.WebCore/WebAttribute/SegmentAttribute.cs @@ -7,7 +7,7 @@ namespace WebExpress.WebCore.WebAttribute /// A static path segment. /// [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] - public class SegmentAttribute : Attribute, IResourceAttribute, ISegmentAttribute + public class SegmentAttribute : Attribute, IEndpointAttribute, ISegmentAttribute { /// /// Returns or set the segment of the uri path. @@ -20,7 +20,7 @@ public class SegmentAttribute : Attribute, IResourceAttribute, ISegmentAttribute private string Display { get; set; } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The segment of the uri path. /// The display string. diff --git a/src/WebExpress.WebCore/WebAttribute/SegmentDoubleAttribute.cs b/src/WebExpress.WebCore/WebAttribute/SegmentDoubleAttribute.cs index 96d700f..1c2a208 100644 --- a/src/WebExpress.WebCore/WebAttribute/SegmentDoubleAttribute.cs +++ b/src/WebExpress.WebCore/WebAttribute/SegmentDoubleAttribute.cs @@ -3,7 +3,11 @@ namespace WebExpress.WebCore.WebAttribute { - public class SegmentDoubleAttribute : Attribute, IResourceAttribute, ISegmentAttribute + /// + /// Attribute to define a double segment in a URI path. + /// + [AttributeUsage(AttributeTargets.Class)] + public class SegmentDoubleAttribute : Attribute, IEndpointAttribute, ISegmentAttribute { /// /// Returns or sets the name of the variable. @@ -16,7 +20,7 @@ public class SegmentDoubleAttribute : Attribute, IResourceAttribute, ISegmentAtt private string Display { get; set; } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The name of the variable. /// The display string. @@ -32,18 +36,6 @@ public SegmentDoubleAttribute(string variableName, string display) /// The path segment. public IUriPathSegment ToPathSegment() { - //var expression = @"^[+-]?(\d*,\d+|\d+(,\d*)?)( +[eE][+-]?\d+)?$"; - - //var callBackDisplay = new Func((segment, moduleId, culture) => - //{ - // return null; - //}); - - //var callBackValiables = new Func>(segment => - //{ - // return null; - //}); - return new UriPathSegmentVariableDouble(VariableName, Display); } } diff --git a/src/WebExpress.WebCore/WebAttribute/SegmentGuidAttribute.cs b/src/WebExpress.WebCore/WebAttribute/SegmentGuidAttribute.cs index 47538bd..57d1d7d 100644 --- a/src/WebExpress.WebCore/WebAttribute/SegmentGuidAttribute.cs +++ b/src/WebExpress.WebCore/WebAttribute/SegmentGuidAttribute.cs @@ -8,7 +8,8 @@ namespace WebExpress.WebCore.WebAttribute /// A dynamic path segment of type guid. /// [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] - public class SegmentGuidAttribute : Attribute, IResourceAttribute, ISegmentAttribute where T : Parameter + public class SegmentGuidAttribute : Attribute, IEndpointAttribute, ISegmentAttribute + where TParameter : Parameter { /// /// Returns or sets the name of the variable. @@ -26,14 +27,13 @@ public class SegmentGuidAttribute : Attribute, IResourceAttribute, ISegmentAt private UriPathSegmentVariableGuid.Format DisplayFormat { get; set; } /// - /// Constructor + /// Initializes a new instance of the class. /// - /// The type of the variable. /// The display string. /// The display format. public SegmentGuidAttribute(string display, UriPathSegmentVariableGuid.Format displayFormat = UriPathSegmentVariableGuid.Format.Simple) { - VariableName = (Activator.CreateInstance(typeof(T)) as Parameter)?.Key?.ToLower(); + VariableName = (Activator.CreateInstance() as Parameter)?.Key?.ToLower(); Display = display; DisplayFormat = displayFormat; } diff --git a/src/WebExpress.WebCore/WebAttribute/SegmentIntAttribute.cs b/src/WebExpress.WebCore/WebAttribute/SegmentIntAttribute.cs index 542535f..ed888a2 100644 --- a/src/WebExpress.WebCore/WebAttribute/SegmentIntAttribute.cs +++ b/src/WebExpress.WebCore/WebAttribute/SegmentIntAttribute.cs @@ -3,7 +3,11 @@ namespace WebExpress.WebCore.WebAttribute { - public class SegmentIntAttribute : Attribute, IResourceAttribute, ISegmentAttribute + /// + /// Attribute to define an integer segment in a URI path. + /// + [AttributeUsage(AttributeTargets.Class)] + public class SegmentIntAttribute : Attribute, IEndpointAttribute, ISegmentAttribute { /// /// Returns or sets the name of the variable. @@ -16,7 +20,7 @@ public class SegmentIntAttribute : Attribute, IResourceAttribute, ISegmentAttrib private string Display { get; set; } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The name of the variable. /// The display string. diff --git a/src/WebExpress.WebCore/WebAttribute/SegmentStringAttribute.cs b/src/WebExpress.WebCore/WebAttribute/SegmentStringAttribute.cs index 2d7c5a2..d125473 100644 --- a/src/WebExpress.WebCore/WebAttribute/SegmentStringAttribute.cs +++ b/src/WebExpress.WebCore/WebAttribute/SegmentStringAttribute.cs @@ -3,7 +3,11 @@ namespace WebExpress.WebCore.WebAttribute { - public class SegmentStringAttribute : Attribute, IResourceAttribute, ISegmentAttribute + /// + /// Attribute to define a segment string in a URI path. + /// + [AttributeUsage(AttributeTargets.Class)] + public class SegmentStringAttribute : Attribute, IEndpointAttribute, ISegmentAttribute { /// /// Returns or sets the name of the variable. @@ -16,7 +20,7 @@ public class SegmentStringAttribute : Attribute, IResourceAttribute, ISegmentAtt private string Display { get; set; } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The name of the variable. /// The display string. @@ -32,18 +36,6 @@ public SegmentStringAttribute(string variableName, string display) /// The path segment. public IUriPathSegment ToPathSegment() { - //var expression = "^[^\"]*$"; - - //var callBackDisplay = new Func((segment, moduleId, culture) => - //{ - // return null; - //}); - - //var callBackValiables = new Func>(segment => - //{ - // return null; - //}); - return new UriPathSegmentVariableString(VariableName, Display); } } diff --git a/src/WebExpress.WebCore/WebAttribute/SegmentUIntAttribute.cs b/src/WebExpress.WebCore/WebAttribute/SegmentUIntAttribute.cs index 408e1dc..fc6b841 100644 --- a/src/WebExpress.WebCore/WebAttribute/SegmentUIntAttribute.cs +++ b/src/WebExpress.WebCore/WebAttribute/SegmentUIntAttribute.cs @@ -3,7 +3,14 @@ namespace WebExpress.WebCore.WebAttribute { - public class SegmentUIntAttribute : Attribute, IResourceAttribute, ISegmentAttribute + /// + /// Attribute to define a segment with an unsigned integer variable in the URI path. + /// + /// + /// This attribute is used to specify a segment in the URI path that contains an unsigned integer variable. + /// + [AttributeUsage(AttributeTargets.Class)] + public class SegmentUIntAttribute : Attribute, IEndpointAttribute, ISegmentAttribute { /// /// Returns or sets the name of the variable. @@ -16,7 +23,7 @@ public class SegmentUIntAttribute : Attribute, IResourceAttribute, ISegmentAttri private string Display { get; set; } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The name of the variable. /// The display string. @@ -32,23 +39,6 @@ public SegmentUIntAttribute(string variableName, string display) /// The path segment. public IUriPathSegment ToPathSegment() { - //var expression = @"^\d$"; - - //var callBackDisplay = new Func((segment, moduleId, culture) => - //{ - // return Display; - //}); - - //var callBackValiables = new Func>(segment => - //{ - // var dict = new Dictionary - // { - // { VariableName, segment } - // }; - - // return dict; - //}); - return new UriPathSegmentVariableUInt(VariableName, Display); } } diff --git a/src/WebExpress.WebCore/WebAttribute/SettingCategoryAttribute.cs b/src/WebExpress.WebCore/WebAttribute/SettingCategoryAttribute.cs new file mode 100644 index 0000000..ee798b9 --- /dev/null +++ b/src/WebExpress.WebCore/WebAttribute/SettingCategoryAttribute.cs @@ -0,0 +1,20 @@ +using System; +using WebExpress.WebCore.WebSettingPage; + +namespace WebExpress.WebCore.WebAttribute +{ + /// + /// Attribute to specify the category in which the settings page is associated. + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] + public class SettingCategoryAttribute : Attribute, ISettingGroupAttribute + where TCategory : class, ISettingCategory + { + /// + /// Initializes a new instance of the class. + /// + public SettingCategoryAttribute() + { + } + } +} diff --git a/src/WebExpress.WebCore/WebAttribute/SettingGroupAttribute.cs b/src/WebExpress.WebCore/WebAttribute/SettingGroupAttribute.cs new file mode 100644 index 0000000..5da22f1 --- /dev/null +++ b/src/WebExpress.WebCore/WebAttribute/SettingGroupAttribute.cs @@ -0,0 +1,20 @@ +using System; +using WebExpress.WebCore.WebSettingPage; + +namespace WebExpress.WebCore.WebAttribute +{ + /// + /// Attribute to define a setting group in which the settings page is associated. + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] + public class SettingGroupAttribute : Attribute, IEndpointAttribute + where TGroup : class, ISettingGroup + { + /// + /// Initializes a new instance of the class. + /// + public SettingGroupAttribute() + { + } + } +} diff --git a/src/WebExpress.WebCore/WebAttribute/SettingHideAttribute.cs b/src/WebExpress.WebCore/WebAttribute/SettingHideAttribute.cs new file mode 100644 index 0000000..837fa1c --- /dev/null +++ b/src/WebExpress.WebCore/WebAttribute/SettingHideAttribute.cs @@ -0,0 +1,16 @@ +namespace WebExpress.WebCore.WebAttribute +{ + /// + /// Represents an attribute that hides a setting in the web UI. + /// + public class SettingHideAttribute : System.Attribute, IEndpointAttribute + { + /// + /// Initializes a new instance of the class. + /// + public SettingHideAttribute() + { + + } + } +} diff --git a/src/WebExpress.WebCore/WebAttribute/SettingSectionAttribute.cs b/src/WebExpress.WebCore/WebAttribute/SettingSectionAttribute.cs new file mode 100644 index 0000000..7fc68fb --- /dev/null +++ b/src/WebExpress.WebCore/WebAttribute/SettingSectionAttribute.cs @@ -0,0 +1,21 @@ +using System; +using WebExpress.WebCore.WebSettingPage; + +namespace WebExpress.WebCore.WebAttribute +{ + /// + /// Attribute to specify the section where the settings page is listed. + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] + public class SettingSectionAttribute : Attribute, IEndpointAttribute, ISettingCategoryAttribute, ISettingGroupAttribute + { + /// + /// Initializes a new instance of the class. + /// + /// The section where the settings page is listed. + public SettingSectionAttribute(SettingSection section) + { + + } + } +} diff --git a/src/WebExpress.WebCore/WebAttribute/StatusCodeAttribute.cs b/src/WebExpress.WebCore/WebAttribute/StatusCodeAttribute.cs index 0543993..44e838b 100644 --- a/src/WebExpress.WebCore/WebAttribute/StatusCodeAttribute.cs +++ b/src/WebExpress.WebCore/WebAttribute/StatusCodeAttribute.cs @@ -1,17 +1,23 @@ -using System; - -namespace WebExpress.WebCore.WebAttribute +namespace WebExpress.WebCore.WebAttribute { - [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] - public class StatusCodeAttribute : System.Attribute, IApplicationAttribute + /// + /// Specifies the status code for an HTTP response (see RFC 7231). + /// + [System.AttributeUsage(System.AttributeTargets.Class, AllowMultiple = false)] + public class StatusCodeAttribute : System.Attribute { /// - /// Constructor + /// Gets the status code. /// - /// The status code. - public StatusCodeAttribute(int status) - { + public int StatusCode { get; } + /// + /// Initializes a new instance of the class with the specified status code. + /// + /// The status code. + public StatusCodeAttribute(int code) + { + StatusCode = code; } } } diff --git a/src/WebExpress.WebCore/WebAttribute/StatusResponseAttribute.cs b/src/WebExpress.WebCore/WebAttribute/StatusResponseAttribute.cs new file mode 100644 index 0000000..27220cb --- /dev/null +++ b/src/WebExpress.WebCore/WebAttribute/StatusResponseAttribute.cs @@ -0,0 +1,22 @@ +using System; +using WebExpress.WebCore.WebMessage; + +namespace WebExpress.WebCore.WebAttribute +{ + /// + /// Specifies the status code for a starus page. + /// + /// The type of the response. + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] + public class StatusResponseAttribute : Attribute, IStatusPageAttribute + where TResponse : Response, new() + { + /// + /// Initializes a new instance of the class. + /// + public StatusResponseAttribute() + { + + } + } +} diff --git a/src/WebExpress.WebCore/WebAttribute/SystemPluginAttribute.cs b/src/WebExpress.WebCore/WebAttribute/SystemPluginAttribute.cs index 4efd157..1962657 100644 --- a/src/WebExpress.WebCore/WebAttribute/SystemPluginAttribute.cs +++ b/src/WebExpress.WebCore/WebAttribute/SystemPluginAttribute.cs @@ -9,7 +9,7 @@ namespace WebExpress.WebCore.WebAttribute public class SystemPluginAttribute : Attribute { /// - /// Constructor + /// Initializes a new instance of the class. /// public SystemPluginAttribute() { diff --git a/src/WebExpress.WebCore/WebAttribute/ThemeModeAttribute.cs b/src/WebExpress.WebCore/WebAttribute/ThemeModeAttribute.cs new file mode 100644 index 0000000..8baa04c --- /dev/null +++ b/src/WebExpress.WebCore/WebAttribute/ThemeModeAttribute.cs @@ -0,0 +1,21 @@ +using System; +using WebExpress.WebCore.WebTheme; + +namespace WebExpress.WebCore.WebAttribute +{ + /// + /// Attribute to specify the theme mode. + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] + public class ThemeModeAttribute : Attribute, IThemeAttribute + { + /// + /// Initializes a new instance of the class with the specified theme mode. + /// + /// The theme mode to be applied. + public ThemeModeAttribute(ThemeMode mode) + { + + } + } +} diff --git a/src/WebExpress.WebCore/WebAttribute/ThemeStyleAttribute.cs b/src/WebExpress.WebCore/WebAttribute/ThemeStyleAttribute.cs new file mode 100644 index 0000000..1235cb6 --- /dev/null +++ b/src/WebExpress.WebCore/WebAttribute/ThemeStyleAttribute.cs @@ -0,0 +1,20 @@ +using System; + +namespace WebExpress.WebCore.WebAttribute +{ + /// + /// Specifies the style for a theme. + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] + public class ThemeStyleAttribute : Attribute, IThemeAttribute + { + /// + /// Initializes a new instance of the class with the specified URI. + /// + /// The URI of the css theme style. + public ThemeStyleAttribute(string uri) + { + + } + } +} diff --git a/src/WebExpress.WebCore/WebAttribute/TitleAttribute.cs b/src/WebExpress.WebCore/WebAttribute/TitleAttribute.cs index 6fa9aeb..0fdf584 100644 --- a/src/WebExpress.WebCore/WebAttribute/TitleAttribute.cs +++ b/src/WebExpress.WebCore/WebAttribute/TitleAttribute.cs @@ -1,14 +1,17 @@ namespace WebExpress.WebCore.WebAttribute { - public class TitleAttribute : System.Attribute, IResourceAttribute + /// + /// Represents an attribute that assigns a title to a page, setting page, or status page. + /// + [System.AttributeUsage(System.AttributeTargets.Class)] + public class TitleAttribute : System.Attribute, IPageAttribute, ISettingPageAttribute, IStatusPageAttribute { /// - /// Constructor + /// Initializes a new instance of the class. /// /// The display text. public TitleAttribute(string display) { - } } } diff --git a/src/WebExpress.WebCore/WebAttribute/WebIconAttribute.cs b/src/WebExpress.WebCore/WebAttribute/WebIconAttribute.cs new file mode 100644 index 0000000..a110a65 --- /dev/null +++ b/src/WebExpress.WebCore/WebAttribute/WebIconAttribute.cs @@ -0,0 +1,22 @@ +using System; +using WebExpress.WebCore.WebIcon; + +namespace WebExpress.WebCore.WebAttribute +{ + /// + /// Attribute to specify an icon for a plugin, application, or status page. + /// + /// The type of the icon, which must implement the interface. + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] + public class WebIconAttribute : Attribute, ISettingPageAttribute, ISettingCategoryAttribute, ISettingGroupAttribute + where TIcon : IIcon + { + /// + /// Initializes a new instance of the class. + /// + public WebIconAttribute() + { + + } + } +} diff --git a/src/WebExpress.WebCore/WebComponent/ComponentActivator.cs b/src/WebExpress.WebCore/WebComponent/ComponentActivator.cs new file mode 100644 index 0000000..ed44dfd --- /dev/null +++ b/src/WebExpress.WebCore/WebComponent/ComponentActivator.cs @@ -0,0 +1,328 @@ +using System; +using System.Linq; +using System.Reflection; +using WebExpress.WebCore.WebApplication; +using WebExpress.WebCore.WebMessage; +using WebExpress.WebCore.WebStatusPage; + +namespace WebExpress.WebCore.WebComponent +{ + /// + /// Provides methods to create instances of components with dependency injection. + /// + public static class ComponentActivator + { + /// + /// Creates an instance of the specified response type with the component hub and advanced parameters. + /// + /// The type of the response. + /// The type of the response to create. + /// The reference to the context of the host. + /// The component hub to use for dependency injection. + /// Additional parameter with a status message to pass to the response's constructor. + /// Additional parameters to pass to the component's constructor. + /// An instance of the specified response type. + public static T CreateInstance(Type responseType, IHttpServerContext httpServerContext, IComponentHub componentHub, StatusMessage statusMessage, params object[] advancedParameters) where T : Response + { + var flags = BindingFlags.NonPublic | BindingFlags.Instance; + var constructors = responseType?.GetConstructors(flags); + + if (constructors != null) + { + foreach (var constructor in constructors.OrderByDescending(x => x.GetParameters().Length)) + { + // injection + var parameters = constructor.GetParameters(); + var properties = componentHub.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance); + + var parameterValues = parameters.Select(parameter => + parameter.ParameterType == typeof(StatusMessage) ? statusMessage : + parameter.ParameterType == typeof(IComponentHub) ? componentHub : + parameter.ParameterType == typeof(IHttpServerContext) ? httpServerContext : + properties.Where(x => x.PropertyType == parameter.ParameterType) + .FirstOrDefault()? + .GetValue(componentHub) ?? + advancedParameters.Where(x => x.GetType() == parameter.ParameterType) + .FirstOrDefault() ?? null + ).ToArray(); + + if (constructor.Invoke(parameterValues) is T component) + { + return component; + } + } + } + + return Activator.CreateInstance(responseType) as T; + } + + /// + /// Creates an instance of the specified component type with the provided context, component hub advanced parameters. + /// + /// The type of the component manager, which must implement . + /// The reference to the context of the host. + /// Additional parameters to pass to the component's constructor. + /// An instance of the specified component type. + public static T CreateInstance(IHttpServerContext httpServerContext, params object[] advancedParameters) where T : class, IComponentHub + { + var flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; + var constructors = typeof(T).GetConstructors(flags); + + if (constructors != null) + { + foreach (var constructor in constructors.OrderByDescending(x => x.GetParameters().Length)) + { + // injection + var parameters = constructor.GetParameters(); + + var parameterValues = parameters.Select(parameter => + parameter.ParameterType == typeof(IHttpServerContext) ? httpServerContext : + + advancedParameters.Where(x => x.GetType() == parameter.ParameterType) + .FirstOrDefault() ?? null + ).ToArray(); + + if (constructor.Invoke(parameterValues) is T component) + { + return component; + } + } + } + + return Activator.CreateInstance(typeof(T), advancedParameters) as T; + } + + /// + /// Creates an instance of the specified component type with the provided context, component hub advanced parameters. + /// + /// The type of the component manager, which must implement . + /// The type of the component to create. + /// The reference to the context of the host. + /// The component hub to use for dependency injection. + /// Additional parameters to pass to the component's constructor. + /// An instance of the specified component type. + public static T CreateInstance(Type componentType, IHttpServerContext httpServerContext, IComponentHub componentHub, params object[] advancedParameters) where T : class, IComponentManager + { + var flags = BindingFlags.NonPublic | BindingFlags.Instance; + var constructors = componentType?.GetConstructors(flags); + + if (constructors != null) + { + foreach (var constructor in constructors.OrderByDescending(x => x.GetParameters().Length)) + { + // injection + var parameters = constructor.GetParameters(); + var properties = componentHub.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance); + + var parameterValues = parameters.Select(parameter => + parameter.ParameterType == typeof(IComponentHub) ? componentHub : + parameter.ParameterType == typeof(IHttpServerContext) ? httpServerContext : + properties.Where(x => x.PropertyType == parameter.ParameterType) + .FirstOrDefault()? + .GetValue(componentHub) ?? + advancedParameters.Where(x => x.GetType() == parameter.ParameterType) + .FirstOrDefault() ?? null + ).ToArray(); + + if (constructor.Invoke(parameterValues) is T component) + { + return component; + } + } + } + + return Activator.CreateInstance(componentType) as T; + } + + /// + /// Creates an instance of the specified component type with the provided context, component hub advanced parameters. + /// + /// The type of the component manager, which must implement . + /// The reference to the context of the host. + /// The component hub to use for dependency injection. + /// The type of the component to create. + /// Additional parameters to pass to the component's constructor. + /// An instance of the specified component type. + public static T CreateInstance(IHttpServerContext httpServerContext, IComponentHub componentHub, Type componentType, params object[] advancedParameters) where T : IComponent + { + var flags = BindingFlags.NonPublic | BindingFlags.Instance; + var constructors = componentType?.GetConstructors(flags); + + if (constructors != null) + { + foreach (var constructor in constructors.OrderByDescending(x => x.GetParameters().Length)) + { + // injection + var parameters = constructor.GetParameters(); + var properties = componentHub.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance); + + var parameterValues = parameters.Select(parameter => + parameter.ParameterType == typeof(IComponentHub) ? componentHub : + parameter.ParameterType == typeof(IHttpServerContext) ? httpServerContext : + properties.Where(x => x.PropertyType == parameter.ParameterType) + .FirstOrDefault()? + .GetValue(componentHub) ?? + advancedParameters.Where(x => x.GetType() == parameter.ParameterType) + .FirstOrDefault() ?? null + ).ToArray(); + + if (constructor.Invoke(parameterValues) is T component) + { + return component; + } + } + } + + return (T)Activator.CreateInstance(componentType); + } + + /// + /// Creates an instance of the specified component type with the provided context, component hub advanced parameters. + /// + /// The type of the component, which must implement . + /// The reference to the context of the host. + /// The component hub to use for dependency injection. + /// Additional parameters to pass to the component's constructor. + /// An instance of the specified component type. + public static T CreateInstance(IHttpServerContext httpServerContext, IComponentHub componentHub, params object[] advancedParameters) where T : class, IComponent + { + var flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; + var componentType = typeof(T); + var constructors = componentType?.GetConstructors(flags); + + if (constructors != null) + { + foreach (var constructor in constructors.OrderByDescending(x => x.GetParameters().Length)) + { + // injection + var parameters = constructor.GetParameters(); + var properties = componentHub.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance); + + var parameterValues = parameters.Select(parameter => + parameter.ParameterType == typeof(IComponentHub) ? componentHub : + parameter.ParameterType == typeof(IHttpServerContext) ? httpServerContext : + properties.Where(x => x.PropertyType == parameter.ParameterType) + .FirstOrDefault()? + .GetValue(componentHub) ?? + advancedParameters.Where(x => x != null) + .Where(x => x.GetType() == parameter.ParameterType) + .FirstOrDefault() ?? null + ).ToArray(); + + if (constructor.Invoke(parameterValues) is T component) + { + return component; + } + } + } + + return Activator.CreateInstance(componentType) as T; + } + + /// + /// Creates an instance of the specified component type with the provided context and component hub and advanced parameters. + /// + /// The type of the component, which must implement . + /// The type of the context, which must implement . + /// The type of the component to create. + /// The context to pass to the component's constructor. + /// The reference to the context of the host. + /// The component hub to use for dependency injection. + /// Additional parameters to pass to the component's constructor. + /// An instance of the specified component type. + public static TComponent CreateInstance(Type componentType, TContext context, IHttpServerContext httpServerContext, IComponentHub componentHub, params object[] advancedParameters) + where TComponent : class, IComponent + where TContext : IContext + { + var flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; + var constructors = componentType?.GetConstructors(flags); + + if (constructors != null) + { + foreach (var constructor in constructors.OrderByDescending(x => x.GetParameters().Length)) + { + // injection + var parameters = constructor.GetParameters(); + var hubProperties = componentHub.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance); + var contextIdProperty = context.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance) + .Where(x => x.PropertyType == typeof(IComponentId)) + .FirstOrDefault(); + + var parameterValues = parameters.Select(parameter => + parameter.ParameterType == typeof(IComponentHub) ? componentHub : + parameter.ParameterType == typeof(IHttpServerContext) ? httpServerContext : + parameter.ParameterType == typeof(TContext) ? context : + parameter.ParameterType == typeof(IComponentId) ? contextIdProperty?.GetValue(context) : + hubProperties.Where(x => x.PropertyType == parameter.ParameterType) + .FirstOrDefault()? + .GetValue(componentHub) ?? + advancedParameters.Where(x => + x.GetType() == parameter.ParameterType || + ( + parameter.ParameterType == typeof(IApplicationContext) && + x.GetType().GetInterfaces().Any(x => x == typeof(IApplicationContext)) + ) + ) + .FirstOrDefault() ?? null + ).ToArray(); + + if (constructor.Invoke(parameterValues) is TComponent component) + { + return component; + } + } + } + + return Activator.CreateInstance(componentType) as TComponent; + } + + /// + /// Creates an instance of the specified component type with the provided context and component hub and advanced parameters. + /// + /// The type of the context, which must implement . + /// The type of the component to create. + /// The context to pass to the component's constructor. + /// The reference to the context of the host. + /// The component hub to use for dependency injection. + /// Additional parameters to pass to the component's constructor. + /// An instance of the specified component type. + public static IComponent CreateInstance(Type componentType, TContext context, IHttpServerContext httpServerContext, IComponentHub componentHub, params object[] advancedParameters) + where TContext : IContext + { + var flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; + var constructors = componentType?.GetConstructors(flags); + + if (constructors != null) + { + foreach (var constructor in constructors.OrderByDescending(x => x.GetParameters().Length)) + { + // injection + var parameters = constructor.GetParameters(); + var properties = componentHub.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance); + var contextIdProperty = context.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance) + .Where(x => x.PropertyType == typeof(IComponentId)) + .FirstOrDefault(); + + var parameterValues = parameters.Select(parameter => + parameter.ParameterType == typeof(IComponentHub) ? componentHub : + parameter.ParameterType == typeof(IHttpServerContext) ? httpServerContext : + parameter.ParameterType == typeof(TContext) ? context : + parameter.ParameterType == typeof(IComponentId) ? contextIdProperty?.GetValue(context) : + properties.Where(x => x.PropertyType == parameter.ParameterType) + .FirstOrDefault()? + .GetValue(componentHub) ?? + advancedParameters.Where(x => x.GetType() == parameter.ParameterType) + .FirstOrDefault() ?? null + ).ToArray(); + + if (constructor.Invoke(parameterValues) is IComponent component) + { + return component; + } + } + } + + return Activator.CreateInstance(componentType) as IComponent; + } + } +} diff --git a/src/WebExpress.WebCore/WebComponent/ComponentHub.cs b/src/WebExpress.WebCore/WebComponent/ComponentHub.cs new file mode 100644 index 0000000..cbbea77 --- /dev/null +++ b/src/WebExpress.WebCore/WebComponent/ComponentHub.cs @@ -0,0 +1,540 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using WebExpress.WebCore.Internationalization; +using WebExpress.WebCore.WebApplication; +using WebExpress.WebCore.WebAsset; +using WebExpress.WebCore.WebComponent.Model; +using WebExpress.WebCore.WebEndpoint; +using WebExpress.WebCore.WebEvent; +using WebExpress.WebCore.WebFragment; +using WebExpress.WebCore.WebIdentity; +using WebExpress.WebCore.WebJob; +using WebExpress.WebCore.WebLog; +using WebExpress.WebCore.WebPackage; +using WebExpress.WebCore.WebPage; +using WebExpress.WebCore.WebPlugin; +using WebExpress.WebCore.WebResource; +using WebExpress.WebCore.WebRestApi; +using WebExpress.WebCore.WebSession; +using WebExpress.WebCore.WebSettingPage; +using WebExpress.WebCore.WebSitemap; +using WebExpress.WebCore.WebStatusPage; +using WebExpress.WebCore.WebTask; +using WebExpress.WebCore.WebTheme; + +namespace WebExpress.WebCore.WebComponent +{ + /// + /// Central management of components. + /// + public class ComponentHub : IComponentHub + { + private readonly IHttpServerContext _httpServerContext; + private readonly ComponentDictionary _dictionary = []; + private readonly LogManager _logManager; + private readonly PackageManager _packageManager; + private readonly InternationalizationManager _internationalizationManager; + private readonly PluginManager _pluginManager; + private readonly ApplicationManager _applicationManager; + private readonly EndpointManager _endpointManager; + private readonly AssetManager _assetManager; + private readonly ResourceManager _resourceManager; + private readonly PageManager _pageManager; + private readonly SettingPageManager _settingPageManager; + private readonly RestApiManager _restApiManager; + private readonly SitemapManager _sitemapManager; + private readonly FragmentManager _fragmentManager; + private readonly StatusPageManager _statusPageManager; + private readonly SessionManager _sessionManager; + private readonly EventManager _eventManager; + private readonly JobManager _jobManager; + private readonly TaskManager _taskManager; + private readonly IdentityManager _identityManager; + private readonly ThemeManager _themeManager; + private int _lastCounter = 0; + + /// + /// An event that fires when an component is added. + /// + public event EventHandler AddComponent; + + /// + /// An event that fires when an component is removed. + /// + public event EventHandler RemoveComponent; + + /// + /// Returns all registered managers. + /// + public IEnumerable Managers => new IComponentManager[] + { + _logManager, + _packageManager, + _pluginManager, + _applicationManager, + _endpointManager, + _sitemapManager, + _fragmentManager, + _assetManager, + _resourceManager, + _pageManager, + _settingPageManager, + _restApiManager, + _eventManager, + _jobManager, + _statusPageManager, + _internationalizationManager, + _identityManager, + _sessionManager, + _taskManager, + _themeManager + }.Concat(_dictionary.Values.SelectMany(x => x).Select(x => x.ComponentInstance)); + + /// + /// Returns the log manager. + /// + /// The instance of the log manager. + public ILogManager LogManager => _logManager; + + /// + /// Returns the package manager. + /// + /// The instance of the package manager. + public IPackageManager PackageManager => _packageManager; + + /// + /// Returns the plugin manager. + /// + /// The instance of the plugin manager. + public IPluginManager PluginManager => _pluginManager; + + /// + /// Returns the application manager. + /// + /// The instance of the application manager. + public IApplicationManager ApplicationManager => _applicationManager; + + /// + /// Returns the event manager. + /// + /// The instance of the event manager. + public IEventManager EventManager => _eventManager; + + /// + /// Returns the job manager. + /// + /// The instance of the job manager. + public IJobManager JobManager => _jobManager; + + /// + /// Returns the task manager. + /// + /// The instance of the task manager. + public ITaskManager TaskManager => _taskManager; + + /// + /// Returns the endpoint manager. + /// + /// The instance of the endpoint manager. + public IEndpointManager EndpointManager => _endpointManager; + + /// + /// Returns the asset manager. + /// + /// The instance of the asset manager. + public IAssetManager AssetManager => _assetManager; + + /// + /// Returns the resource manager. + /// + /// The instance of the resource manager. + public IResourceManager ResourceManager => _resourceManager; + + /// + /// Returns the page manager. + /// + /// The instance of the page manager. + public IPageManager PageManager => _pageManager; + + /// + /// Returns the setting page manager. + /// + /// The instance of the setting page manager. + public ISettingPageManager SettingPageManager => _settingPageManager; + + /// + /// Returns the rest api manager. + /// + /// The instance of the rest api manager. + public IRestApiManager RestApiManager => _restApiManager; + + /// + /// Returns the sitemap manager. + /// + /// The instance of the sitemap manager. + public ISitemapManager SitemapManager => _sitemapManager; + + /// + /// Returns the fragment manager. + /// + /// The instance of the fragment manager. + public IFragmentManager FragmentManager => _fragmentManager; + + /// + /// Returns the status page manager. + /// + /// The instance of the status page manager. + public IStatusPageManager StatusPageManager => _statusPageManager; + + /// + /// Returns the internationalization manager. + /// + /// The instance of the internationalization manager. + public IInternationalizationManager InternationalizationManager => _internationalizationManager; + + /// + /// Returns the identity manager. + /// + /// The instance of the identity manager. + public IIdentityManager IdentityManager => _identityManager; + + /// + /// Returns the session manager. + /// + /// The instance of the session manager. + public ISessionManager SessionManager => _sessionManager; + + /// + /// Returns the theme manager. + /// + /// The instance of the theme manager. + public IThemeManager ThemeManager => _themeManager; + + /// + /// Initializes a new instance of the class. + /// + /// The reference to the context of the host. + protected ComponentHub(IHttpServerContext httpServerContext) + { + _httpServerContext = httpServerContext; + + // order is relevant + _pluginManager = CreateInstance(typeof(PluginManager)) as PluginManager; + _packageManager = CreateInstance(typeof(PackageManager)) as PackageManager; + + _logManager = CreateInstance(typeof(LogManager)) as LogManager; + _internationalizationManager = CreateInstance(typeof(InternationalizationManager)) as InternationalizationManager; + _applicationManager = CreateInstance(typeof(ApplicationManager)) as ApplicationManager; + _sitemapManager = CreateInstance(typeof(SitemapManager)) as SitemapManager; + _fragmentManager = CreateInstance(typeof(FragmentManager)) as FragmentManager; + _endpointManager = CreateInstance(typeof(EndpointManager)) as EndpointManager; + _assetManager = CreateInstance(typeof(AssetManager)) as AssetManager; + _resourceManager = CreateInstance(typeof(ResourceManager)) as ResourceManager; + _pageManager = CreateInstance(typeof(PageManager)) as PageManager; + _settingPageManager = CreateInstance(typeof(SettingPageManager)) as SettingPageManager; + _restApiManager = CreateInstance(typeof(RestApiManager)) as RestApiManager; + _statusPageManager = CreateInstance(typeof(StatusPageManager)) as StatusPageManager; + _eventManager = CreateInstance(typeof(EventManager)) as EventManager; + _jobManager = CreateInstance(typeof(JobManager)) as JobManager; + _sessionManager = CreateInstance(typeof(SessionManager)) as SessionManager; + _taskManager = CreateInstance(typeof(TaskManager)) as TaskManager; + _identityManager = CreateInstance(typeof(IdentityManager)) as IdentityManager; + _themeManager = CreateInstance(typeof(ThemeManager)) as ThemeManager; + + _internationalizationManager.Register(typeof(HttpServer).Assembly, typeof(HttpServer).Assembly.GetName().Name?.ToLower()); + + _httpServerContext.Log.Debug + ( + _internationalizationManager.Translate("webexpress.webcore:componentmanager.initialization") + ); + + _pluginManager.AddPlugin += (sender, pluginContext) => + { + Register(pluginContext); + }; + + PluginManager.RemovePlugin += (sender, pluginContext) => + { + Remove(pluginContext); + }; + } + + /// + /// Creates and initializes a component. + /// + /// The component class. + /// The instance of the create and initialized component. + private IComponentManager CreateInstance(Type componentType) + { + if (componentType == null) + { + return null; + } + else if (!componentType.GetInterfaces().Where(x => x == typeof(IComponentManager)).Any()) + { + _httpServerContext.Log.Warning + ( + _internationalizationManager.Translate + ( + "webexpress.webcore:componentmanager.wrongtype", + componentType?.FullName, typeof(IComponentManager).FullName + ) + ); + + return null; + } + + try + { + return ComponentActivator.CreateInstance(componentType, _httpServerContext, this); + } + catch (Exception ex) + { + _httpServerContext.Log.Exception(ex); + } + + return null; + } + + /// + /// Returns a component based on its id. + /// + /// The id. + /// The instance of the component or null. + public IComponentManager GetComponentManager(string id) + { + return _dictionary.Values + .SelectMany(x => x) + .Where(x => x.ComponentId.Equals(id, StringComparison.OrdinalIgnoreCase)) + .Select(x => x.ComponentInstance) + .FirstOrDefault(); + } + + /// + /// Returns a component based on its type. + /// + /// The component class. + /// The instance of the component or null. + public TComponentManager GetComponentManager() + where TComponentManager : IComponentManager + { + return (TComponentManager)_dictionary.Values + .SelectMany(x => x) + .Where(x => x.ComponentClass == typeof(TComponentManager)) + .Select(x => x.ComponentInstance) + .FirstOrDefault(); + } + + /// + /// Discovers and registers the components from the specified plugin. + /// + /// A plugin context that contain the components. + internal void Register(IPluginContext pluginContext) + { + // the plugin has already been registered + if (_dictionary.ContainsKey(pluginContext)) + { + return; + } + + var assembly = pluginContext.Assembly; + + _dictionary.Add(pluginContext, []); + var componentItems = _dictionary[pluginContext]; + + foreach (var type in assembly.GetExportedTypes().Where(x => x.IsClass && x.IsSealed && x.GetInterface(typeof(IComponentManager).Name) != null)) + { + var id = type.FullName?.ToLower(); + + // determining attributes + var componentInstance = CreateInstance(type); + + if (!componentItems.Where(x => x.ComponentId.Equals(id, StringComparison.OrdinalIgnoreCase)).Any()) + { + _dictionary[pluginContext] = componentItems.Concat([ new ComponentItem() + { + ComponentClass = type, + ComponentId = id, + ComponentInstance = componentInstance + }]); + + _httpServerContext.Log.Debug + ( + _internationalizationManager.Translate("webexpress.webcore:componentmanager.register", id) + ); + + // raises the AddComponent event + OnAddComponent(componentInstance); + } + else + { + _httpServerContext.Log.Warning + ( + _internationalizationManager.Translate("webexpress.webcore:componentmanager.duplicate", id) + ); + } + } + + Log(); + } + + /// + /// Boots the components. + /// + /// The plugin context. + internal void BootComponent(IPluginContext pluginContext) + { + _pluginManager.Boot(pluginContext); + _applicationManager.Boot(pluginContext); + + foreach (var component in _dictionary.Values + .Where(x => x is IExecutableElements) + .Select(x => x as IExecutableElements)) + { + component.Boot(pluginContext); + } + } + + /// + /// Boots the components. + /// + /// A enumeration of plugin contexts. + internal void BootComponent(IEnumerable pluginContexts) + { + foreach (var pluginContext in pluginContexts) + { + BootComponent(pluginContext); + } + } + + /// + /// Starts the component. + /// + public void Execute() + { + _httpServerContext.Log.Debug + ( + _internationalizationManager.Translate("webexpress.webcore:componentmanager.execute") + ); + + _packageManager.Execute(); + _jobManager.Execute(); + } + + /// + /// Shutting down the component manager. + /// + public void ShutDown() + { + _httpServerContext.Log.Debug + ( + _internationalizationManager.Translate("webexpress.webcore:componentmanager.shutdown") + ); + } + + /// + /// Shutting down the component. + /// + /// The plugin context. + internal void ShutDownComponent(IPluginContext pluginContext) + { + _pluginManager.ShutDown(pluginContext); + _applicationManager.ShutDown(pluginContext); + + foreach (var component in _dictionary.Values + .Where(x => x is IExecutableElements) + .Select(x => x as IExecutableElements)) + { + component.ShutDown(pluginContext); + } + } + + /// + /// Removes all components associated with the specified plugin context. + /// + /// The context of the plugin that contains the applications to remove. + public void Remove(IPluginContext pluginContext) + { + if (pluginContext == null) + { + return; + } + + if (_dictionary.TryGetValue(pluginContext, out IEnumerable componentItems)) + { + if (!componentItems.Any()) + { + return; + } + + foreach (var componentItem in componentItems) + { + OnRemoveComponent(componentItem.ComponentInstance); + + _httpServerContext.Log.Debug + ( + _internationalizationManager.Translate("webexpress.webcore:componentmanager.remove") + ); + } + } + + _dictionary.Remove(pluginContext); + } + + /// + /// Raises the AddComponent event. + /// + /// The component. + private void OnAddComponent(IComponentManager component) + { + AddComponent?.Invoke(null, component); + } + + /// + /// Raises the RemoveComponent event. + /// + /// The component. + private void OnRemoveComponent(IComponentManager component) + { + RemoveComponent?.Invoke(null, component); + } + + /// + /// Output of the components to the log. + /// + private void Log() + { + if (_lastCounter == Managers.Count()) + { + return; + } + + using var frame = new LogFrameSimple(_httpServerContext.Log); + var output = new List + { + _internationalizationManager.Translate("webexpress.webcore:componentmanager.component") + }; + + foreach (var manager in Managers) + { + output.Add + ( + string.Empty.PadRight(2) + + _internationalizationManager.Translate("webexpress.webcore:componentmanager.name", manager.GetType()?.Name.ToLower()) + ); + } + + _httpServerContext.Log.Info(string.Join(Environment.NewLine, output)); + _lastCounter = Managers.Count(); + } + + /// + /// Release of unmanaged resources reserved during use. + /// + public void Dispose() + { + GC.SuppressFinalize(this); + } + } +} diff --git a/src/WebExpress.WebCore/WebComponent/ComponentId.cs b/src/WebExpress.WebCore/WebComponent/ComponentId.cs new file mode 100644 index 0000000..65fb013 --- /dev/null +++ b/src/WebExpress.WebCore/WebComponent/ComponentId.cs @@ -0,0 +1,143 @@ +namespace WebExpress.WebCore.WebComponent +{ + /// + /// Represents a component identifier. + /// + public class ComponentId : IComponentId + { + private readonly string _id; + + /// + /// Initializes a new instance of the class with the specified identifier. + /// + /// The identifier of the component. + public ComponentId(string id) + { + _id = id?.ToLower(); + } + + /// + /// Defines an implicit conversion of a string to a . + /// + /// The identifier of the component. + /// A new instance of initialized with the specified identifier. + public static implicit operator ComponentId(string id) + { + return new ComponentId(id); + } + + /// + /// Defines an implicit conversion of a to a string. + /// + /// The to convert. + /// The identifier of the component as a string. + public static implicit operator string(ComponentId componentId) + { + return componentId._id; + } + + /// + /// Determines whether two specified objects have the same value. + /// + /// The first to compare. + /// The second to compare. + /// true if the value of is the same as the value of ; otherwise, false. + public static bool operator ==(ComponentId lhs, IComponentId rhs) + { + if (ReferenceEquals(lhs, rhs)) + { + return true; + } + if (lhs is null || rhs is null) + { + return false; + } + + return lhs.ToString().Equals(rhs.ToString()); + } + + /// + /// Determines whether two specified objects have the same value. + /// + /// The first to compare. + /// The second string to compare. + /// true if the value of is the same as the value of ; otherwise, false. + public static bool operator ==(ComponentId lhs, string rhs) + { + if (ReferenceEquals(lhs, rhs)) + { + return true; + } + if (lhs is null || rhs is null) + { + return false; + } + + return lhs._id == rhs; + } + + /// + /// Determines whether two specified objects have different values. + /// + /// The first to compare. + /// The second to compare. + /// true if the value of is different from the value of ; otherwise, false. + public static bool operator !=(ComponentId lhs, IComponentId rhs) + { + return !(lhs == rhs); + } + + /// + /// Determines whether two specified objects have different values. + /// + /// The first to compare. + /// The second string to compare. + /// true if the value of is different from the value of ; otherwise, false. + public static bool operator !=(ComponentId lhs, string rhs) + { + return !(lhs == rhs); + } + + /// + /// Determines whether the specified object is equal to the current . + /// + /// The object to compare with the current . + /// true if the specified object is equal to the current ; otherwise, false. + public bool Equals(string obj) + { + return _id.Equals(obj); + } + + /// + /// Determines whether the specified object is equal to the current . + /// + /// The object to compare with the current . + /// true if the specified object is equal to the current ; otherwise, false. + public override bool Equals(object obj) + { + if (obj is ComponentId other) + { + return _id == other._id; + } + return false; + } + + /// + /// Serves as the default hash function. + /// + /// A hash code for the current . + public override int GetHashCode() + { + return _id.GetHashCode(); + } + + /// + /// Returns the string representation of the component identifier. + /// + /// The identifier of the component as a string. + public override string ToString() + { + return _id; + } + } +} diff --git a/src/WebExpress.WebCore/WebComponent/ComponentManager.cs b/src/WebExpress.WebCore/WebComponent/ComponentManager.cs deleted file mode 100644 index 4483349..0000000 --- a/src/WebExpress.WebCore/WebComponent/ComponentManager.cs +++ /dev/null @@ -1,488 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using WebExpress.WebCore.Internationalization; -using WebExpress.WebCore.WebApplication; -using WebExpress.WebCore.WebEvent; -using WebExpress.WebCore.WebJob; -using WebExpress.WebCore.WebModule; -using WebExpress.WebCore.WebPackage; -using WebExpress.WebCore.WebPlugin; -using WebExpress.WebCore.WebResource; -using WebExpress.WebCore.WebSession; -using WebExpress.WebCore.WebSitemap; -using WebExpress.WebCore.WebStatusPage; -using WebExpress.WebCore.WebTask; - -namespace WebExpress.WebCore.WebComponent -{ - /// - /// Central management of components. - /// - public static class ComponentManager - { - /// - /// An event that fires when an component is added. - /// - public static event EventHandler AddComponent; - - /// - /// An event that fires when an component is removed. - /// - public static event EventHandler RemoveComponent; - - /// - /// Returns the reference to the context of the host. - /// - public static IHttpServerContext HttpServerContext { get; private set; } - - /// - /// Returns the directory where the components are listed. - /// - private static ComponentDictionary Dictionary { get; } = new ComponentDictionary(); - - /// - /// Returns all registered components. - /// - public static IEnumerable Components => new IComponent[] - { - PackageManager, - PluginManager, - ApplicationManager, - ModuleManager, - EventManager, - JobManager, - ResponseManager, - SitemapManager, - InternationalizationManager, - SessionManager, - TaskManager - }.Concat(Dictionary.Values.SelectMany(x => x).Select(x => x.ComponentInstance)); - - /// - /// Returns the package manager. - /// - /// The instance of the package manager or null. - public static PackageManager PackageManager { get; private set; } - - /// - /// Returns the plugin manager. - /// - /// The instance of the plugin manager or null. - public static PluginManager PluginManager { get; private set; } - - /// - /// Returns the application manager. - /// - /// The instance of the application manager or null. - public static ApplicationManager ApplicationManager { get; private set; } - - /// - /// Returns the module manager. - /// - /// The instance of the module manager or null. - public static ModuleManager ModuleManager { get; private set; } - - /// - /// Returns the event manager. - /// - /// The instance of the event manager or null. - public static EventManager EventManager { get; private set; } - - /// - /// Returns the job manager. - /// - /// The instance of the job manager or null. - public static JobManager JobManager { get; private set; } - - /// - /// Returns the response manager. - /// - /// The instance of the response manager or null. - public static ResponseManager ResponseManager { get; private set; } - - /// - /// Returns the resource manager. - /// - /// The instance of the resource manager or null. - public static ResourceManager ResourceManager { get; private set; } - - /// - /// Returns the sitemap manager. - /// - /// The instance of the sitemap manager or null. - public static SitemapManager SitemapManager { get; private set; } - - /// - /// Returns the internationalization manager. - /// - /// The instance of the internationalization manager or null. - public static InternationalizationManager InternationalizationManager { get; private set; } - - /// - /// Returns the session manager. - /// - /// The instance of the session manager or null. - public static SessionManager SessionManager { get; private set; } - - /// - /// Returns the task manager. - /// - /// The instance of the task manager manager or null. - public static TaskManager TaskManager { get; private set; } - - /// - /// Initialization - /// - /// The reference to the context of the host. - internal static void Initialization(IHttpServerContext httpServerContext) - { - HttpServerContext = httpServerContext; - - InternationalizationManager.Register(typeof(HttpServer).Assembly, "webexpress"); - - HttpServerContext.Log.Debug - ( - InternationalizationManager.I18N("webexpress:componentmanager.initialization") - ); - - // order is relevant - PackageManager = CreateInstance(typeof(PackageManager)) as PackageManager; - PluginManager = CreateInstance(typeof(PluginManager)) as PluginManager; - InternationalizationManager = CreateInstance(typeof(InternationalizationManager)) as InternationalizationManager; - ApplicationManager = CreateInstance(typeof(ApplicationManager)) as ApplicationManager; - ModuleManager = CreateInstance(typeof(ModuleManager)) as ModuleManager; - ResourceManager = CreateInstance(typeof(ResourceManager)) as ResourceManager; - ResponseManager = CreateInstance(typeof(ResponseManager)) as ResponseManager; - EventManager = CreateInstance(typeof(EventManager)) as EventManager; - JobManager = CreateInstance(typeof(JobManager)) as JobManager; - SitemapManager = CreateInstance(typeof(SitemapManager)) as SitemapManager; - SessionManager = CreateInstance(typeof(SessionManager)) as SessionManager; - TaskManager = CreateInstance(typeof(TaskManager)) as TaskManager; - - PluginManager.AddPlugin += (sender, pluginContext) => - { - Register(pluginContext); - }; - - PluginManager.RemovePlugin += (sender, pluginContext) => - { - Remove(pluginContext); - }; - } - - /// - /// Creates and initializes a component. - /// - /// The component class. - /// The instance of the create and initialized component. - private static IComponent CreateInstance(Type componentType) - { - if (componentType == null) - { - return null; - } - else if (!componentType.GetInterfaces().Where(x => x == typeof(IComponent)).Any()) - { - HttpServerContext.Log.Warning - ( - InternationalizationManager.I18N - ( - "webexpress:componentmanager.wrongtype", - componentType?.FullName, typeof(IComponent).FullName - ) - ); - - return null; - } - - try - { - var flags = BindingFlags.NonPublic | BindingFlags.Instance; - var component = componentType?.Assembly.CreateInstance - ( - componentType?.FullName, - false, - flags, - null, - null, - null, - null - ) as IComponent; - - //var component = Activator.CreateInstance(componentType, flags) as IComponent; - - component.Initialization(HttpServerContext); - - return component; - } - catch (Exception ex) - { - HttpServerContext.Log.Exception(ex); - } - - return null; - } - - /// - /// Returns a component based on its id. - /// - /// The id. - /// The instance of the component or null. - public static IComponent GetComponent(string id) - { - return Dictionary.Values - .SelectMany(x => x) - .Where(x => x.ComponentId.Equals(id, StringComparison.OrdinalIgnoreCase)) - .Select(x => x.ComponentInstance) - .FirstOrDefault(); - } - - /// - /// Returns a component based on its type. - /// - /// The component class. - /// The instance of the component or null. - public static T GetComponent() where T : IComponent - { - return (T)Dictionary.Values - .SelectMany(x => x) - .Where(x => x.ComponentClass == typeof(T)) - .Select(x => x.ComponentInstance) - .FirstOrDefault(); - } - - /// - /// Discovers and registers the components from the specified plugin. - /// - /// A plugin context that contain the components. - public static void Register(IPluginContext pluginContext) - { - // the plugin has already been registered - if (Dictionary.ContainsKey(pluginContext)) - { - return; - } - - var assembly = pluginContext.Assembly; - - Dictionary.Add(pluginContext, new List()); - var componentItems = Dictionary[pluginContext]; - - foreach (var type in assembly.GetExportedTypes().Where(x => x.IsClass && x.IsSealed && x.GetInterface(typeof(IComponent).Name) != null)) - { - var id = type.FullName?.ToLower(); - - // determining attributes - var componentInstance = CreateInstance(type); - - if (!componentItems.Where(x => x.ComponentId.Equals(id, StringComparison.OrdinalIgnoreCase)).Any()) - { - componentItems.Add(new ComponentItem() - { - ComponentClass = type, - ComponentId = id, - ComponentInstance = componentInstance - }); - - HttpServerContext.Log.Debug - ( - InternationalizationManager.I18N("webexpress:componentmanager.register", id) - ); - - // raises the AddComponent event - OnAddComponent(componentInstance); - } - else - { - HttpServerContext.Log.Warning - ( - InternationalizationManager.I18N("webexpress:componentmanager.duplicate", id) - ); - } - } - } - - /// - /// Discovers and registers the components from the specified plugins. - /// - /// A list with plugin contexts that contain the components. - public static void Register(IEnumerable pluginContexts) - { - foreach (var pluinContext in pluginContexts) - { - Register(pluinContext); - } - } - - /// - /// Boots the components. - /// - /// The plugin context. - internal static void BootComponent(IPluginContext pluginContext) - { - PluginManager.Boot(pluginContext); - ApplicationManager.Boot(pluginContext); - ModuleManager.Boot(pluginContext); - - foreach (var component in Dictionary.Values - .Where(x => x is IExecutableElements) - .Select(x => x as IExecutableElements)) - { - component.Boot(pluginContext); - } - } - - /// - /// Boots the components. - /// - /// A enumeration of plugin contexts. - internal static void BootComponent(IEnumerable pluginContexts) - { - foreach (var pluginContext in pluginContexts) - { - BootComponent(pluginContext); - } - } - - /// - /// Starts the component. - /// - internal static void Execute() - { - HttpServerContext.Log.Debug - ( - InternationalizationManager.I18N("webexpress:componentmanager.execute") - ); - - PackageManager.Execute(); - JobManager.Execute(); - } - - /// - /// Shutting down the component manager. - /// - public static void ShutDown() - { - HttpServerContext.Log.Debug - ( - InternationalizationManager.I18N("webexpress:componentmanager.shutdown") - ); - } - - /// - /// Shutting down the component. - /// - /// The plugin context. - public static void ShutDownComponent(IPluginContext pluginContext) - { - PluginManager.ShutDown(pluginContext); - ApplicationManager.ShutDown(pluginContext); - ModuleManager.ShutDown(pluginContext); - - foreach (var component in Dictionary.Values - .Where(x => x is IExecutableElements) - .Select(x => x as IExecutableElements)) - { - component.ShutDown(pluginContext); - } - } - - /// - /// Removes all components associated with the specified plugin context. - /// - /// The context of the plugin that contains the applications to remove. - public static void Remove(IPluginContext pluginContext) - { - if (Dictionary.ContainsKey(pluginContext)) - { - return; - } - - var componentItems = Dictionary[pluginContext]; - - if (!componentItems.Any()) - { - return; - } - - foreach (var componentItem in componentItems) - { - OnRemoveComponent(componentItem.ComponentInstance); - - HttpServerContext.Log.Debug - ( - InternationalizationManager.I18N("webexpress:componentmanager.remove") - ); - } - - ModuleManager.Remove(pluginContext); - ApplicationManager.Remove(pluginContext); - PluginManager.Remove(pluginContext); - - Dictionary.Remove(pluginContext); - } - - /// - /// Raises the AddComponent event. - /// - /// The component. - private static void OnAddComponent(IComponent component) - { - AddComponent?.Invoke(null, component); - } - - /// - /// Raises the RemoveComponent event. - /// - /// The component. - private static void OnRemoveComponent(IComponent component) - { - RemoveComponent?.Invoke(null, component); - } - - /// - /// Output of the components to the log. - /// - public static void LogStatus() - { - using var frame = new LogFrameSimple(HttpServerContext.Log); - var output = new List - { - InternationalizationManager.I18N("webexpress:componentmanager.component") - }; - - foreach (var pluginContext in PluginManager.Plugins) - { - output.Add - ( - string.Empty.PadRight(2) + - InternationalizationManager.I18N("webexpress:pluginmanager.plugin", pluginContext.PluginId) - ); - - ApplicationManager.PrepareForLog(pluginContext, output, 4); - ModuleManager.PrepareForLog(pluginContext, output, 4); - ResourceManager.PrepareForLog(pluginContext, output, 4); - ResponseManager.PrepareForLog(pluginContext, output, 4); - JobManager.PrepareForLog(pluginContext, output, 4); - } - - foreach (var item in Dictionary) - { - foreach (var component in item.Value) - { - output.Add - ( - string.Empty.PadRight(2) + - InternationalizationManager.I18N("webexpress:pluginmanager.plugin", item.Key.PluginId) - ); - - component.ComponentInstance?.PrepareForLog(item.Key, output, 4); - } - } - - HttpServerContext.Log.Info(string.Join(Environment.NewLine, output)); - } - } -} diff --git a/src/WebExpress.WebCore/WebComponent/IComponent.cs b/src/WebExpress.WebCore/WebComponent/IComponent.cs index f4228c8..7954ea8 100644 --- a/src/WebExpress.WebCore/WebComponent/IComponent.cs +++ b/src/WebExpress.WebCore/WebComponent/IComponent.cs @@ -1,30 +1,10 @@ -using System.Collections.Generic; -using WebExpress.WebCore.WebPlugin; - -namespace WebExpress.WebCore.WebComponent +namespace WebExpress.WebCore.WebComponent { /// - /// Interface of the manager classes. + /// Interface of a component. /// public interface IComponent { - /// - /// Returns the reference to the context of the host. - /// - static IHttpServerContext HttpServerContext { get; } - - /// - /// Initialization - /// - /// The reference to the context of the host. - void Initialization(IHttpServerContext context); - /// - /// Information about the component is collected and prepared for output in the log. - /// - /// The context of the plugin. - /// A list of log entries. - /// The shaft deep. - void PrepareForLog(IPluginContext pluginContext, IList output, int deep); } } diff --git a/src/WebExpress.WebCore/WebComponent/IComponentHub.cs b/src/WebExpress.WebCore/WebComponent/IComponentHub.cs new file mode 100644 index 0000000..13ac642 --- /dev/null +++ b/src/WebExpress.WebCore/WebComponent/IComponentHub.cs @@ -0,0 +1,181 @@ +using System; +using System.Collections.Generic; +using WebExpress.WebCore.Internationalization; +using WebExpress.WebCore.WebApplication; +using WebExpress.WebCore.WebAsset; +using WebExpress.WebCore.WebEndpoint; +using WebExpress.WebCore.WebEvent; +using WebExpress.WebCore.WebFragment; +using WebExpress.WebCore.WebIdentity; +using WebExpress.WebCore.WebJob; +using WebExpress.WebCore.WebLog; +using WebExpress.WebCore.WebPackage; +using WebExpress.WebCore.WebPage; +using WebExpress.WebCore.WebPlugin; +using WebExpress.WebCore.WebResource; +using WebExpress.WebCore.WebRestApi; +using WebExpress.WebCore.WebSession; +using WebExpress.WebCore.WebSettingPage; +using WebExpress.WebCore.WebSitemap; +using WebExpress.WebCore.WebStatusPage; +using WebExpress.WebCore.WebTask; +using WebExpress.WebCore.WebTheme; + +namespace WebExpress.WebCore.WebComponent +{ + /// + /// Interface of the central management of manager components. + /// + public interface IComponentHub : IComponentManager + { + /// + /// An event that fires when an component is added. + /// + event EventHandler AddComponent; + + /// + /// An event that fires when an component is removed. + /// + event EventHandler RemoveComponent; + + /// + /// Returns all registered components. + /// + IEnumerable Managers { get; } + + /// + /// Returns the log manager. + /// + /// The instance of the log manager. + ILogManager LogManager { get; } + + /// + /// Returns the package manager. + /// + /// The instance of the package manager. + IPackageManager PackageManager { get; } + + /// + /// Returns the plugin manager. + /// + /// The instance of the plugin manager. + public IPluginManager PluginManager { get; } + + /// + /// Returns the application manager. + /// + /// The instance of the application manager. + IApplicationManager ApplicationManager { get; } + + /// + /// Returns the event manager. + /// + /// The instance of the event manager. + IEventManager EventManager { get; } + + /// + /// Returns the job manager. + /// + /// The instance of the job manager. + IJobManager JobManager { get; } + + /// + /// Returns the task manager. + /// + /// The instance of the task manager. + ITaskManager TaskManager { get; } + + /// + /// Returns the endpoint manager. + /// + /// The instance of the endpoint manager. + IEndpointManager EndpointManager { get; } + + /// + /// Returns the asset manager. + /// + /// The instance of the asset manager. + public IAssetManager AssetManager { get; } + + /// + /// Returns the resource manager. + /// + /// The instance of the resource manager. + IResourceManager ResourceManager { get; } + + /// + /// Returns the page manager. + /// + /// The instance of the page manager. + IPageManager PageManager { get; } + + /// + /// Returns the setting page manager. + /// + /// The instance of the setting page manager. + ISettingPageManager SettingPageManager { get; } + + /// + /// Returns the rest api manager. + /// + /// The instance of the rest api manager. + IRestApiManager RestApiManager { get; } + + /// + /// Returns the sitemap manager. + /// + /// The instance of the sitemap manager. + ISitemapManager SitemapManager { get; } + + /// + /// Returns the fragment manager. + /// + /// The instance of the fragment manager. + IFragmentManager FragmentManager { get; } + + /// + /// Returns the status page manager. + /// + /// The instance of the status page manager. + IStatusPageManager StatusPageManager { get; } + + /// + /// Returns the internationalization manager. + /// + /// The instance of the internationalization manager. + IInternationalizationManager InternationalizationManager { get; } + + /// + /// Returns the identity manager. + /// + /// The instance of the identity manager. + IIdentityManager IdentityManager { get; } + + /// + /// Returns the session manager. + /// + /// The instance of the session manager. + ISessionManager SessionManager { get; } + + /// + /// Returns the theme manager. + /// + /// The instance of the theme manager. + IThemeManager ThemeManager { get; } + + /// + /// Returns a component based on its id. + /// + /// The id. + /// The instance of the component. + IComponentManager GetComponentManager(string id); + + /// + /// Returns a component based on its type. + /// + /// The component class. + /// The instance of the component. + TComponentManager GetComponentManager() + where TComponentManager : IComponentManager; + } +} diff --git a/src/WebExpress.WebCore/WebComponent/IComponentId.cs b/src/WebExpress.WebCore/WebComponent/IComponentId.cs new file mode 100644 index 0000000..e4229d0 --- /dev/null +++ b/src/WebExpress.WebCore/WebComponent/IComponentId.cs @@ -0,0 +1,9 @@ +namespace WebExpress.WebCore.WebComponent +{ + /// + /// Represents an interface for component identifiers. + /// + public interface IComponentId + { + } +} diff --git a/src/WebExpress.WebCore/WebComponent/IComponentManager.cs b/src/WebExpress.WebCore/WebComponent/IComponentManager.cs new file mode 100644 index 0000000..e4555e5 --- /dev/null +++ b/src/WebExpress.WebCore/WebComponent/IComponentManager.cs @@ -0,0 +1,11 @@ +using System; + +namespace WebExpress.WebCore.WebComponent +{ + /// + /// Interface of the manager classes. + /// + public interface IComponentManager : IDisposable + { + } +} diff --git a/src/WebExpress.WebCore/WebComponent/IComponentPlugin.cs b/src/WebExpress.WebCore/WebComponent/IComponentManagerPlugin.cs similarity index 94% rename from src/WebExpress.WebCore/WebComponent/IComponentPlugin.cs rename to src/WebExpress.WebCore/WebComponent/IComponentManagerPlugin.cs index 46981bc..9366404 100644 --- a/src/WebExpress.WebCore/WebComponent/IComponentPlugin.cs +++ b/src/WebExpress.WebCore/WebComponent/IComponentManagerPlugin.cs @@ -6,7 +6,7 @@ namespace WebExpress.WebCore.WebComponent /// /// Interface of the manager classes. /// - public interface IComponentPlugin : IComponent + public interface IComponentManagerPlugin : IComponentManager { /// /// Discovers and registers entries from the specified plugin. diff --git a/src/WebExpress.WebCore/WebComponent/IContext.cs b/src/WebExpress.WebCore/WebComponent/IContext.cs new file mode 100644 index 0000000..cb0ac42 --- /dev/null +++ b/src/WebExpress.WebCore/WebComponent/IContext.cs @@ -0,0 +1,9 @@ +namespace WebExpress.WebCore.WebComponent +{ + /// + /// Represents the interface for all web contexts. + /// + public interface IContext + { + } +} diff --git a/src/WebExpress.WebCore/WebComponent/ComponentDictionary.cs b/src/WebExpress.WebCore/WebComponent/Model/ComponentDictionary.cs similarity index 60% rename from src/WebExpress.WebCore/WebComponent/ComponentDictionary.cs rename to src/WebExpress.WebCore/WebComponent/Model/ComponentDictionary.cs index 57ff804..d49fc29 100644 --- a/src/WebExpress.WebCore/WebComponent/ComponentDictionary.cs +++ b/src/WebExpress.WebCore/WebComponent/Model/ComponentDictionary.cs @@ -1,14 +1,14 @@ using System.Collections.Generic; using WebExpress.WebCore.WebPlugin; -namespace WebExpress.WebCore.WebComponent +namespace WebExpress.WebCore.WebComponent.Model { /// /// Internal management of components. /// key = plugin /// value = component item /// - public class ComponentDictionary : Dictionary> + internal class ComponentDictionary : Dictionary> { } diff --git a/src/WebExpress.WebCore/WebComponent/ComponentItem.cs b/src/WebExpress.WebCore/WebComponent/Model/ComponentItem.cs similarity index 66% rename from src/WebExpress.WebCore/WebComponent/ComponentItem.cs rename to src/WebExpress.WebCore/WebComponent/Model/ComponentItem.cs index d46af63..a3df3b5 100644 --- a/src/WebExpress.WebCore/WebComponent/ComponentItem.cs +++ b/src/WebExpress.WebCore/WebComponent/Model/ComponentItem.cs @@ -1,7 +1,10 @@ using System; -namespace WebExpress.WebCore.WebComponent +namespace WebExpress.WebCore.WebComponent.Model { + /// + /// Represents an item of a web component, including its class type, ID, and instance. + /// public class ComponentItem { /// @@ -17,10 +20,10 @@ public class ComponentItem /// /// Returns the component instance or null if not already created. /// - public IComponent ComponentInstance { get; internal set; } + public IComponentManager ComponentInstance { get; internal set; } /// - /// Constructor + /// Initializes a new instance of the class. /// internal ComponentItem() { diff --git a/src/WebExpress.WebCore/WebEndpoint/EndpointManager.cs b/src/WebExpress.WebCore/WebEndpoint/EndpointManager.cs new file mode 100644 index 0000000..a87b971 --- /dev/null +++ b/src/WebExpress.WebCore/WebEndpoint/EndpointManager.cs @@ -0,0 +1,253 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Reflection; +using WebExpress.WebCore.Internationalization; +using WebExpress.WebCore.WebApplication; +using WebExpress.WebCore.WebAttribute; +using WebExpress.WebCore.WebComponent; +using WebExpress.WebCore.WebIcon; +using WebExpress.WebCore.WebMessage; +using WebExpress.WebCore.WebUri; + +namespace WebExpress.WebCore.WebEndpoint +{ + /// + /// The endpoint manager manages WebExpress elements, which can be called with a URI (Uniform Resource Identifier). + /// + public sealed class EndpointManager : IEndpointManager, ISystemComponent + { + private static readonly string[] _namespacePrefixes = ["page", "pages", "webpage", "webpages", "website", "www", "web"]; + private static readonly string _indexPrefix = "index"; + private static readonly string[] _classSuffixes = ["page"]; + private readonly IHttpServerContext _httpServerContext; + private readonly Dictionary _registrations = []; + + /// + /// An event that fires when an endpoint is added. + /// + public event EventHandler AddEndpoint; + + /// + /// An event that fires when an endpoint is removed. + /// + public event EventHandler RemoveEndpoint; + + /// + /// Returns all endpoints contexts. + /// + public IEnumerable Endpoints => _registrations.Values.SelectMany(x => x.EndpointsResolver()); + + /// + /// Initializes a new instance of the class. + /// + /// The component hub. + /// The reference to the context of the host. + [SuppressMessage("CodeQuality", "IDE0051:Remove unused private members", Justification = "Used via Reflection.")] + private EndpointManager(IComponentHub componentHub, IHttpServerContext httpServerContext) + { + //_componentHub = componentHub; + + _httpServerContext = httpServerContext; + + _httpServerContext.Log.Debug + ( + I18N.Translate("webexpress.webcore:endpointmanager.initialization") + ); + } + + /// + /// Registers an endpoint context type. + /// + /// The type of the endpoint context. + /// The registration details containing the callback functions. + public void Register(EndpointRegistration endpointRegistration) where TEndpointContext : IEndpointContext + { + var type = typeof(TEndpointContext); + if (!_registrations.ContainsKey(type)) + { + _registrations[type] = endpointRegistration; + + endpointRegistration.AddEndpoint += OnAddEndpoint; + endpointRegistration.RemoveEndpoint += OnRemoveEndpoint; + } + } + + /// + /// Removes the registration for a specific endpoint context type. + /// + /// The type of the endpoint context. + public void Remove() where TEndpointContext : IEndpointContext + { + var type = typeof(TEndpointContext); + _registrations.Remove(type, out var endpointRegistration); + + endpointRegistration.AddEndpoint -= OnAddEndpoint; + endpointRegistration.RemoveEndpoint -= OnRemoveEndpoint; + } + + /// + /// Returns an enumeration of endpoint contexts. + /// + /// The endpoint type. + /// The application context. + /// An enumeration of endpoint contexts. + public IEnumerable GetEndpoints(Type endpointType, IApplicationContext applicationContext = null) + { + if (endpointType == null) + { + return []; + } + + return _registrations.SelectMany(x => x.Value.EndpointResolver(endpointType, applicationContext)); + } + + /// + /// Handles a request and returns a response. + /// + /// The request to handle. + /// The context of the endpoint handling the request. + /// The response generated by the endpoint. + public Response HandleRequest(Request request, IEndpointContext endpointContext) + { + var registration = _registrations + .Where(x => x.Key == endpointContext?.GetType()) + .Select(x => x.Value) + .FirstOrDefault(); + + return registration?.HandleRequest(request, endpointContext); + } + + /// + /// Raises the event when an endpoint is added. + /// + /// The source of the event. + /// The context of the endpoint being added. + private void OnAddEndpoint(object sender, IEndpointContext endpointContext) + { + AddEndpoint?.Invoke(sender, endpointContext); + } + + /// + /// Raises the event when an endpoint is removed. + /// + /// The source of the event. + /// The context of the endpoint being removed. + private void OnRemoveEndpoint(object sender, IEndpointContext endpointContext) + { + RemoveEndpoint?.Invoke(sender, endpointContext); + } + + /// + /// Release of unmanaged resources reserved during use. + /// + public void Dispose() + { + } + + /// + /// Returns the route of an endpoint based on the class type, application context and segment attributes. + /// + /// The type of the class. + /// The application context route. + /// The segment attribute. + /// The intermediate segments. + /// The namespace prefixes. + /// The route of the endpoint. + public static IRoute CreateEndpointRoute + ( + Type classType, + IRoute contextRoute, + ISegmentAttribute segment, + IEnumerable intermediateSegments = null, + string[] namespacePrefixes = null + ) + { + var assemblyName = classType.Assembly.GetName().Name; + var fullClassName = classType.FullName; + var className = _classSuffixes?.FirstOrDefault(s => classType.Name.ToLowerInvariant().EndsWith(s, StringComparison.OrdinalIgnoreCase)) is string suffix + ? classType.Name.ToLowerInvariant()[..^suffix.Length] + : classType.Name.ToLowerInvariant(); + var segments = (fullClassName.Length - classType.Name.Length - 1 > assemblyName.Length) + ? fullClassName[(assemblyName.Length + 1)..^(classType.Name.Length + 1)].ToLowerInvariant().Split('.', StringSplitOptions.RemoveEmptyEntries) + : []; + + var segmentMapping = segments.Select((segment, index) => new + { + FullNamespace = $"{assemblyName}.{string.Join(".", segments.Take(index + 1))}", + Segment = segment + }); + + var segmentAttributesMapping = segmentMapping.Select(s => + { + var segmentResult = default(IUriPathSegment); + var name = default(string); + var description = default(string); + var icon = default(IIcon); + + var typeName = $"{s.FullNamespace}.Index"; + var segmentInfoType = classType.Assembly.GetType(typeName, throwOnError: false, ignoreCase: true); + + if (segmentInfoType != null) + { + var segAttrType = segmentInfoType.CustomAttributes + .Where(x => x.AttributeType.GetInterfaces().Contains(typeof(ISegmentAttribute))) + .Select(x => x.AttributeType) + .FirstOrDefault(); + + var segInstance = segAttrType != null + ? segmentInfoType.GetCustomAttribute(segAttrType, false) as ISegmentAttribute + : null; + var nameAttr = segmentInfoType.CustomAttributes + .FirstOrDefault(x => x.AttributeType == typeof(TitleAttribute)); + var descAttr = segmentInfoType.CustomAttributes + .FirstOrDefault(x => x.AttributeType == typeof(DescriptionAttribute)); + var iconAttr = segmentInfoType.CustomAttributes + .FirstOrDefault(x => x.AttributeType.IsGenericType && + x.AttributeType.GetGenericTypeDefinition() == typeof(WebIconAttribute<>)); + + segmentResult = segInstance?.ToPathSegment(); + name = nameAttr?.ConstructorArguments.FirstOrDefault().Value?.ToString(); + description = descAttr?.ConstructorArguments.FirstOrDefault().Value?.ToString(); + icon = iconAttr != null + ? Activator.CreateInstance(iconAttr.AttributeType.GenericTypeArguments.FirstOrDefault()) as IIcon + : null; + } + + return new + { + Segment = segmentResult ?? new UriPathSegmentConstant(s.Segment), + Name = name, + Description = description, + Icon = icon + }; + }); + + segmentAttributesMapping = segmentAttributesMapping.Any() && _namespacePrefixes + .Contains(segmentAttributesMapping + .First().Segment + .ToString()) + ? segmentAttributesMapping.Skip(1) + : segmentAttributesMapping; + + segmentAttributesMapping = segmentAttributesMapping.Any() && (namespacePrefixes ?? []) + .Contains(segmentAttributesMapping + .First().Segment + .ToString()) + ? segmentAttributesMapping.Skip(1) + : segmentAttributesMapping; + + var endpointRoute = (intermediateSegments ?? []) + .Concat(segmentAttributesMapping.Where(x => !x.Segment.IsEmpty).Select(x => x.Segment)); + + var classSegment = !className.StartsWith(_indexPrefix) + ? segment?.ToPathSegment() ?? new UriPathSegmentConstant(className) + : null; + var uri = RouteEndpoint.Combine(contextRoute, endpointRoute) + .Concat(classSegment); + + return uri; + } + } +} diff --git a/src/WebExpress.WebCore/WebEndpoint/EndpointRegistration.cs b/src/WebExpress.WebCore/WebEndpoint/EndpointRegistration.cs new file mode 100644 index 0000000..72b82b6 --- /dev/null +++ b/src/WebExpress.WebCore/WebEndpoint/EndpointRegistration.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using WebExpress.WebCore.WebApplication; +using WebExpress.WebCore.WebMessage; + +namespace WebExpress.WebCore.WebEndpoint +{ + /// + /// Contains the registration details for an endpoint, including factory and resolver functions. + /// + public class EndpointRegistration + { + /// + /// Stores the event handler for adding an endpoint. + /// + public EventHandler AddEndpoint { get; set; } + + /// + /// Stores the event handler for removing an endpoint. + /// + public EventHandler RemoveEndpoint { get; set; } + + /// + /// Returns or sets the context resolver function to resolve the corresponding endpoint contexts. + /// + public Func> EndpointResolver { get; set; } + + /// + /// Returns or sets the endpoint resolver function to resolve additional endpoint contexts. + /// + public Func> EndpointsResolver { get; set; } + + /// + /// Returns or sets the function to handle requests. + /// + public Func HandleRequest { get; set; } + } +} diff --git a/src/WebExpress.WebCore/WebEndpoint/IEndpoint.cs b/src/WebExpress.WebCore/WebEndpoint/IEndpoint.cs new file mode 100644 index 0000000..8db24c3 --- /dev/null +++ b/src/WebExpress.WebCore/WebEndpoint/IEndpoint.cs @@ -0,0 +1,11 @@ +using WebExpress.WebCore.WebComponent; + +namespace WebExpress.WebCore.WebEndpoint +{ + /// + /// Defines the base contract for resources of all kinds, such as REST APIs or Pages. + /// + public interface IEndpoint : IComponent + { + } +} diff --git a/src/WebExpress.WebCore/WebEndpoint/IEndpointContext.cs b/src/WebExpress.WebCore/WebEndpoint/IEndpointContext.cs new file mode 100644 index 0000000..0a37b8f --- /dev/null +++ b/src/WebExpress.WebCore/WebEndpoint/IEndpointContext.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; +using WebExpress.WebCore.WebApplication; +using WebExpress.WebCore.WebComponent; +using WebExpress.WebCore.WebCondition; +using WebExpress.WebCore.WebPlugin; + +namespace WebExpress.WebCore.WebEndpoint +{ + /// + /// Represents the context of a endpoint. + /// + public interface IEndpointContext : IContext + { + /// + /// Returns the endpoint id. + /// + IComponentId EndpointId { get; } + + /// + /// Returns the associated plugin context. + /// + IPluginContext PluginContext { get; } + + /// + /// Returns the corresponding application context. + /// + IApplicationContext ApplicationContext { get; } + + /// + /// Provides the conditions that must be met for the resource to be active. + /// + IEnumerable Conditions { get; } + + /// + /// Determines whether the resource is created once and reused each time it is called. + /// + bool Cache { get; } + + /// + /// Returns or sets whether all subpaths should be taken into sitemap. + /// + bool IncludeSubPaths { get; } + + /// + /// Returns the internal routing path for the endpoint. + /// + IRoute Route { get; } + + /// + /// Returns the attributes associated with the page. + /// + IEnumerable Attributes { get; } + } +} diff --git a/src/WebExpress.WebCore/WebEndpoint/IEndpointManager.cs b/src/WebExpress.WebCore/WebEndpoint/IEndpointManager.cs new file mode 100644 index 0000000..bf11c67 --- /dev/null +++ b/src/WebExpress.WebCore/WebEndpoint/IEndpointManager.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using WebExpress.WebCore.WebApplication; +using WebExpress.WebCore.WebComponent; +using WebExpress.WebCore.WebMessage; + +namespace WebExpress.WebCore.WebEndpoint +{ + /// + /// Represents a endpoint manager. + /// + public interface IEndpointManager : IComponentManager + { + /// + /// Returns all endpoints contexts. + /// + IEnumerable Endpoints { get; } + + /// + /// Registers an endpoint context type. + /// + /// The type of the endpoint context. + /// The registration details containing the callback functions. + void Register(EndpointRegistration registration) where T : IEndpointContext; + + /// + /// Removes the registration for a specific endpoint context type. + /// + /// The type of the endpoint context. + void Remove() where T : IEndpointContext; + + /// + /// Returns an enumeration of endpoint contexts. + /// + /// The endpoint type. + /// The application context. + /// An enumeration of endpoint contexts. + IEnumerable GetEndpoints(Type endpointType, IApplicationContext applicationContext = null); + + ///// + ///// Creates a new instance or if caching is active, a possibly existing instance is returned. + ///// + ///// The endpoint context. + ///// The uri. + ///// The search context. + ///// The created endpoint. + //IEndpoint CreateEndpoint(IEndpointContext endpointContext, UriResource uri, SearchContext searchContext); + + /// + /// Handles a request and returns a response. + /// + /// The request to handle. + /// The context of the endpoint handling the request. + /// The response generated by the endpoint. + Response HandleRequest(Request request, IEndpointContext endpointContext); + } +} diff --git a/src/WebExpress.WebCore/WebEndpoint/IRoute.cs b/src/WebExpress.WebCore/WebEndpoint/IRoute.cs new file mode 100644 index 0000000..39f8929 --- /dev/null +++ b/src/WebExpress.WebCore/WebEndpoint/IRoute.cs @@ -0,0 +1,63 @@ +using System.Collections.Generic; +using WebExpress.WebCore.WebMessage; +using WebExpress.WebCore.WebUri; + +namespace WebExpress.WebCore.WebEndpoint +{ + /// + /// A route in WebExpress represents a logical internal path or pattern used to map endpoints within the sitemap. + /// + /// While a URI (Uniform Resource Identifier) defines the complete, standardized external identifier of a resource + /// (e.g., http://example.com/path?query=value#fragment) and thus represents the external address of an endpoint, + /// a route represents the internal address of an endpoint that is used exclusively for internal purposes such as routing + /// and request processing. + /// + /// A route contains neither a scheme, nor an authority, nor other external components. Furthermore, it may include + /// placeholders for dynamic segments (e.g., /users/{id}). + /// + public interface IRoute + { + /// + /// The segments of the route (e.g., /over/there). + /// + IEnumerable PathSegments { get; } + + /// + /// Returns a string representation of the route. + /// + string Display { get; } + + /// + /// Determines if the route is the root. + /// + bool IsRoot { get; } + + /// + /// Concatenates the given path segment to the current route and returns a new instance of IRoute with the updated path. + /// + /// The path segment to be concatenated with the existing route. + /// A new IRoute instance representing the route after concatenation. + IRoute Concat(string segment); + + /// + /// Concatenates the given path segment to the current route and returns a new instance of IRoute with the updated path. + /// + /// An array of path segments to be concatenated to the existing route. + /// A new IRoute instance representing the route after concatenation. + IRoute Concat(params IUriPathSegment[] segments); + + /// + /// Removes a specified segment from the route and returns a new instance of IRoute with the updated path. + /// + /// The path segment to be removed from the existing route. + /// A new IRoute instance representing the route after the segment removal. + IRoute RemoveSegment(string segments); + + /// + /// Converts the route to a URI. + /// + /// The parameters to be included in the URI. + /// An instance of IUri representing the route as a URI. + IUri ToUri(params Parameter[] parameters); + } +} diff --git a/src/WebExpress.WebCore/WebEndpoint/RouteEndpoint.cs b/src/WebExpress.WebCore/WebEndpoint/RouteEndpoint.cs new file mode 100644 index 0000000..9666f69 --- /dev/null +++ b/src/WebExpress.WebCore/WebEndpoint/RouteEndpoint.cs @@ -0,0 +1,295 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using WebExpress.WebCore.WebMessage; +using WebExpress.WebCore.WebUri; + +namespace WebExpress.WebCore.WebEndpoint +{ + /// + /// A route in WebExpress represents a logical internal path or pattern used to map endpoints within the sitemap. + /// + /// While a URI (Uniform Resource Identifier) defines the complete, standardized external identifier of a resource + /// (e.g., http://example.com/path?query=value#fragment) and thus represents the external address of an endpoint, + /// a route represents the internal address of an endpoint that is used exclusively for internal purposes such as routing + /// and request processing. + /// + /// A route contains neither a scheme, nor an authority, nor other external components. Furthermore, it may include + /// placeholders for dynamic segments (e.g., /users/{id}). + /// + public partial class RouteEndpoint : IRoute + { + /// + /// The segments of the route (e.g., /over/there). + /// + public IEnumerable PathSegments { get; private set; } = [new UriPathSegmentRoot()]; + + /// + /// Returns a string representation of the route. + /// + public string Display { get; set; } + + /// + /// Determines if the route is the root. + /// + public bool IsRoot => PathSegments.Count() == 1; + + /// + /// Initializes a new instance of the class. + /// + public RouteEndpoint() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The scheme (e.g. Http, FTP). + /// The authority (e.g. user@example.com:8080). + /// The uri. + public RouteEndpoint(UriScheme scheme, UriAuthority authority, string uri) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The route. + public RouteEndpoint(string route) + { + if (string.IsNullOrWhiteSpace(route) || route == "/") return; + + PathSegments = PathSegments + .Concat(route.Split('/', StringSplitOptions.RemoveEmptyEntries) + .Select(x => new UriPathSegmentConstant(x))); + } + + /// + /// Copy constructor + /// + /// The route. + public RouteEndpoint(IRoute route) + { + PathSegments = route?.PathSegments.Select(x => x.Copy()) ?? []; + } + + /// + /// Initializes a new instance of the class. + /// + /// The path segments. + public RouteEndpoint(params IUriPathSegment[] segments) + { + if (segments.Length > 0) + { + PathSegments = PathSegments + .Concat(segments.Where(x => !x.IsEmpty).Select(x => x.Copy())); + } + } + + /// + /// Initializes a new instance of the class. + /// + /// The base route. + /// The path segments. + public RouteEndpoint(IRoute route, params string[] segments) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The base route. + /// The path segments. + public RouteEndpoint(IRoute route, params IUriPathSegment[] segments) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The route. + /// The path segments. + /// Other segments. + public RouteEndpoint(IRoute route, IEnumerable segments, IEnumerable extendedSegments) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The scheme (e.g. Http, FTP). + /// The authority (e.g. user@example.com:8080). + /// References a position within a resource (e.g. #Anchor). + /// The query part (e.g. ?title=Uniform_Resource_Identifier). + /// The path segments. + public RouteEndpoint(UriScheme scheme, UriAuthority authority, string fragment, IEnumerable query, IEnumerable segments) + { + } + + /// + /// Concatenates the given path segment to the current route and returns a new instance of IRoute with the updated path. + /// + /// The path segment to be concatenated with the existing route. + /// A new IRoute instance representing the route after concatenation. + public IRoute Concat(string segment) + { + if (string.IsNullOrWhiteSpace(segment)) + { + return this; + } + + var copy = new RouteEndpoint((IRoute)this); + copy.PathSegments = copy.PathSegments + .Concat(segment.Split('/', StringSplitOptions.RemoveEmptyEntries) + .Select(x => new UriPathSegmentConstant(x))); + + return copy; + } + + /// + /// Concatenates the given path segment to the current route and returns a new instance of IRoute with the updated path. + /// + /// An array of path segments to be concatenated to the existing route. + /// A new IRoute instance representing the route after concatenation. + public virtual IRoute Concat(params IUriPathSegment[] segments) + { + if (segments == null || segments.Length == 0) + { + return this; + } + + var copy = new RouteEndpoint((IRoute)this); + copy.PathSegments = copy.PathSegments + .Select(x => x.Copy()) + .Concat(segments.Where(x => x != null).Where(x => !x.IsEmpty)); + + return copy; + } + + /// + /// Removes a specified segment from the route and returns a new instance of IRoute with the updated path. + /// + /// The path segment to be removed from the existing route. + /// A new IRoute instance representing the route after the segment removal. + public virtual IRoute RemoveSegment(string segments) + { + if (string.IsNullOrWhiteSpace(segments) || !ToString().Contains(segments)) + { + return this; + } + + var segmentParts = segments.Split('/', StringSplitOptions.RemoveEmptyEntries); + var copy = new RouteEndpoint((IRoute)this); + + copy.PathSegments = copy.PathSegments + .Where(x => !segmentParts.Contains(x.Value)) + .Select(x => x.Copy()); + + return copy; + } + + /// + /// Converts the route to a URI. + /// + /// The parameters to be included in the URI. + /// An instance of IUri representing the route as a URI. + public IUri ToUri(params Parameter[] parameters) + { + return new UriEndpoint(this).SetParameters(parameters); + } + + /// + /// Combines the specified routes into a compound route. + /// + /// The routes to be combine. + /// A combined route. + public static RouteEndpoint Combine(params IRoute[] routes) + { + var r = routes.Skip(1).Where(x => !x.IsRoot).SelectMany(x => x.PathSegments.Skip(1)); + var copy = new RouteEndpoint(routes.FirstOrDefault()); + copy.PathSegments = copy.PathSegments + .Concat(r); + + return copy; + } + + /// + /// Combines the specified routes into a compound uri. + /// + /// The base route to be used as the starting point. + /// The routes to be combine. + /// A combined route. + public static RouteEndpoint Combine(IRoute baseRoute, params string[] routes) + { + var copy = new RouteEndpoint(baseRoute); + copy.PathSegments = copy.PathSegments + .Concat(routes.Where + ( + x => !string.IsNullOrWhiteSpace(x)) + .SelectMany(x => x.Split('/', StringSplitOptions.RemoveEmptyEntries) + ) + .Select(x => new UriPathSegmentConstant(x) as IUriPathSegment)); + + return copy; + } + + /// + /// Combines the specified base route, intermediate route, and enumerable segments into a compound route. + /// + /// The base route serving as the starting point. + /// The enumerable collection of path segments to be added. + /// A new compound route combining all specified components. + public static RouteEndpoint Combine(IRoute baseRoute, IEnumerable segments) + { + var copy = new RouteEndpoint(baseRoute); + copy.PathSegments = copy.PathSegments + .Concat(segments); + + return copy; + } + + /// + /// Combines the specified base route, intermediate route, and enumerable segments into a compound route. + /// + /// The base route serving as the starting point. + /// The path segment(s) to be added. + /// A new compound route combining all specified components. + public static RouteEndpoint Combine(IRoute baseRoute, string segments) + { + var copy = new RouteEndpoint(baseRoute); + copy.PathSegments = copy.PathSegments + .Concat + ( + segments?.Split('/', StringSplitOptions.RemoveEmptyEntries) + ?.Select(x => new UriPathSegmentConstant(x)) ?? [] + ); + + return copy; + } + + /// + /// Converts a route to a string. + /// + /// The uri to convert. + public static implicit operator string(RouteEndpoint route) + { + return route?.ToString(); + } + + /// + /// Converts the route to a string. + /// + /// A string that represents the current route. + public override string ToString() + { + var path = "/" + string.Join + ( + "/", + PathSegments.Where(x => x is not UriPathSegmentRoot) + .Select(x => x.ToString().TrimStart('/')) + ); + + return path?.TrimEnd('/'); + } + } +} \ No newline at end of file diff --git a/src/WebExpress.WebCore/WebEvent/EventDictionary.cs b/src/WebExpress.WebCore/WebEvent/EventDictionary.cs deleted file mode 100644 index a6bf724..0000000 --- a/src/WebExpress.WebCore/WebEvent/EventDictionary.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System.Collections.Generic; -using WebExpress.WebCore.WebEvent; -using WebExpress.WebCore.WebPlugin; - -namespace WebExpress.WebCore.WebJob -{ - /// - /// key = plugin context - /// value = ressource items - /// - internal class EventDictionary : Dictionary> - { - } -} diff --git a/src/WebExpress.WebCore/WebEvent/EventHandlerContext.cs b/src/WebExpress.WebCore/WebEvent/EventHandlerContext.cs new file mode 100644 index 0000000..9191a50 --- /dev/null +++ b/src/WebExpress.WebCore/WebEvent/EventHandlerContext.cs @@ -0,0 +1,41 @@ +using WebExpress.WebCore.WebApplication; +using WebExpress.WebCore.WebComponent; +using WebExpress.WebCore.WebPlugin; + +namespace WebExpress.WebCore.WebEvent +{ + /// + /// Represents the context of an event. + /// + public class EventHandlerContext : IEventHandlerContext + { + /// + /// Returns the event id. + /// + public IComponentId EventId { get; internal set; } + + /// + /// Returns the event handler id. + /// + public string EventHandlerId { get; internal set; } + + /// + /// Returns the associated plugin context. + /// + public IPluginContext PluginContext { get; internal set; } + + /// + /// Returns the corresponding application context. + /// + public IApplicationContext ApplicationContext { get; internal set; } + + /// + /// Returns a string that represents the current object. + /// + /// A string that represents the current object. + public override string ToString() + { + return $"Event: {EventId}"; + } + } +} diff --git a/src/WebExpress.WebCore/WebEvent/EventManager.cs b/src/WebExpress.WebCore/WebEvent/EventManager.cs index 80a0965..04d6b87 100644 --- a/src/WebExpress.WebCore/WebEvent/EventManager.cs +++ b/src/WebExpress.WebCore/WebEvent/EventManager.cs @@ -1,9 +1,13 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using WebExpress.WebCore.Internationalization; +using WebExpress.WebCore.WebApplication; +using WebExpress.WebCore.WebAttribute; using WebExpress.WebCore.WebComponent; -using WebExpress.WebCore.WebJob; -using WebExpress.WebCore.WebModule; +using WebExpress.WebCore.WebEvent.Model; +using WebExpress.WebCore.WebLog; using WebExpress.WebCore.WebPlugin; namespace WebExpress.WebCore.WebEvent @@ -11,66 +15,137 @@ namespace WebExpress.WebCore.WebEvent /// /// The event manager. /// - public sealed class EventManager : IComponentPlugin, ISystemComponent + public sealed class EventManager : IEventManager, ISystemComponent { + private readonly IComponentHub _componentHub; + private readonly IHttpServerContext _httpServerContext; + private readonly EventDictionary _dictionary = []; + + /// + /// An event that fires when an event handler is added. + /// + public event EventHandler AddEventHandler; + /// - /// Returns or sets the reference to the context of the host. + /// An event that fires when an event handler is removed. /// - public IHttpServerContext HttpServerContext { get; private set; } + public event EventHandler RemoveEventHandler; /// - /// Returns the directory where the events are listed. + /// Returns the collection of events. /// - private EventDictionary Dictionary { get; } = new EventDictionary(); + public IEnumerable EventHandlers => _dictionary.Values + .SelectMany(x => x.Values) + .SelectMany(x => x.Values) + .SelectMany(x => x) + .Select(x => x.EventHandlerContext); /// - /// Constructor + /// Initializes a new instance of the class. /// - internal EventManager() + /// The component hub. + /// The reference to the context of the host. + [SuppressMessage("CodeQuality", "IDE0051:Remove unused private members", Justification = "Used via Reflection.")] + private EventManager(IComponentHub componentHub, IHttpServerContext httpServerContext) { - ComponentManager.PluginManager.AddPlugin += (sender, pluginContext) => - { - Register(pluginContext); - }; + _componentHub = componentHub; - ComponentManager.PluginManager.RemovePlugin += (sender, pluginContext) => - { - Remove(pluginContext); - }; + _componentHub.PluginManager.AddPlugin += OnAddPlugin; + _componentHub.PluginManager.RemovePlugin += OnRemovePlugin; + _componentHub.ApplicationManager.AddApplication += OnAddApplication; + _componentHub.ApplicationManager.RemoveApplication += OnRemoveApplication; + + _httpServerContext = httpServerContext; - ComponentManager.ModuleManager.AddModule += (sender, moduleContext) => + _httpServerContext.Log.Debug + ( + I18N.Translate + ( + "webexpress.webcore:eventmanager.initialization" + ) + ); + } + + /// + /// Returns the event handler contexts. + /// + /// The type of event. + /// The application context. + /// An IEnumerable of event handler contexts. + public IEnumerable GetEventHandlers(IApplicationContext applicationContext) + where TEvent : IEvent + { + return _dictionary.GetEventHandlerItems(applicationContext) + .Select(x => x.EventHandlerContext); + } + + /// + /// Returns the event handler contexts. + /// + /// The application context. + /// The type of event. + /// An IEnumerable of event handler contexts. + public IEnumerable GetEventHandlers(IApplicationContext applicationContext, Type eventType) + { + return _dictionary.GetEventHandlerItems(applicationContext, eventType) + .Select(x => x.EventHandlerContext); + } + + /// + /// Raises the specified event. + /// + /// The type of event. + /// The application context. + /// The sender object. + /// The event argument. + public void RaiseEvent(IApplicationContext applicationContext, object sender, IEventArgument argument) + where TEvent : IEvent + { + var eventHandlers = _dictionary.GetEventHandlerItems(applicationContext); + + foreach (var eventHandler in eventHandlers) { - AssignToModule(moduleContext); - }; + eventHandler?.Process(sender, argument); + } + } - ComponentManager.ModuleManager.RemoveModule += (sender, moduleContext) => + /// + /// Discovers and binds jobs to an application. + /// + /// The context of the plugin whose jobs are to be associated. + private void Register(IPluginContext pluginContext) + { + if (_dictionary.ContainsKey(pluginContext)) { - DetachFromModule(moduleContext); - }; + return; + } + + Register(pluginContext, _componentHub.ApplicationManager.GetApplications(pluginContext)); } /// - /// Initialization + /// Discovers and binds jobs to an application. /// - /// The reference to the context of the host. - public void Initialization(IHttpServerContext context) + /// The context of the application whose jobs are to be associated. + private void Register(IApplicationContext applicationContext) { - HttpServerContext = context; + foreach (var pluginContext in _componentHub.PluginManager.GetPlugins(applicationContext)) + { + if (_dictionary.TryGetValue(pluginContext, out var appDict) && appDict.ContainsKey(applicationContext)) + { + continue; + } - HttpServerContext.Log.Debug - ( - InternationalizationManager.I18N - ( - "webexpress:eventmanager.initialization" - ) - ); + Register(pluginContext, [applicationContext]); + } } /// - /// Discovers and registers event handlers from the specified plugin. + /// Registers resources for a given plugin and application context. /// - /// A context of a plugin whose event handlers are to be registered. - public void Register(IPluginContext pluginContext) + /// The plugin context. + /// The application context (optional). + private void Register(IPluginContext pluginContext, IEnumerable applicationContexts) { var assembly = pluginContext?.Assembly; @@ -79,96 +154,228 @@ public void Register(IPluginContext pluginContext) x => x.IsClass == true && x.IsSealed && x.IsPublic && - x.GetInterface(typeof(IEventHandler).Name) != null + ( + x.GetInterface(typeof(IEventHandler).Name) != null || + x.GetInterface(typeof(IEventHandler<>).Name) != null + ) )) { + var id = eventHandlerType.FullName?.ToLower(); + var eventType = default(Type); + + foreach (var customAttribute in eventHandlerType.CustomAttributes + .Where(x => x.AttributeType.GetInterfaces().Contains(typeof(IEventAttribute)))) + { + if (customAttribute.AttributeType.Name == typeof(EventAttribute<>).Name && customAttribute.AttributeType.Namespace == typeof(EventAttribute<>).Namespace) + { + eventType = customAttribute.AttributeType.GenericTypeArguments.FirstOrDefault(); + } + } + if (eventType == default) + { + _httpServerContext.Log.Debug + ( + I18N.Translate + ( + "webexpress.webcore:eventmanager.eventless", + id + ) + ); + + break; + } + + // assign the event to existing applications + foreach (var applicationContext in applicationContexts) + { + var eventHandlerContext = new EventHandlerContext() + { + EventId = new ComponentId(eventType.FullName), + EventHandlerId = id, + PluginContext = pluginContext, + ApplicationContext = applicationContext + }; + + if (_dictionary.AddEventItem + ( + pluginContext, + applicationContext, + new EventItem(_componentHub, _httpServerContext, pluginContext, applicationContext, eventHandlerContext, eventHandlerType, eventType) + )) + { + OnAddEventHandler(eventHandlerContext); + + _httpServerContext.Log.Debug + ( + I18N.Translate + ( + "webexpress.webcore:eventmanager.register", + id, + applicationContext.ApplicationId + ) + ); + } + else + { + _httpServerContext.Log.Debug + ( + I18N.Translate + ( + "webexpress.webcore:eventmanager.duplicate", + id, + applicationContext.ApplicationId + ) + ); + } + } + } + + Log(); + } + + /// + /// Removes all events associated with the specified plugin context. + /// + /// The context of the plugin that contains the events to remove. + internal void Remove(IPluginContext pluginContext) + { + // the plugin has not been registered in the manager + if (_dictionary.TryGetValue(pluginContext, out var value)) + { + foreach (var eventHandlerItem in value + .SelectMany(x => x.Value) + .SelectMany(x => x.Value)) + { + eventHandlerItem.Dispose(); + } + + _dictionary.Remove(pluginContext); } } /// - /// Discovers and registers entries from the specified plugin. + /// Removes all events associated with the specified application context. /// - /// A list with plugin contexts that contain the jobs. - public void Register(IEnumerable pluginContexts) + /// The context of the application that contains the events to remove. + internal void Remove(IApplicationContext applicationContext) { - foreach (var pluginContext in pluginContexts) + if (applicationContext == null) + { + return; + } + + foreach (var pluginDict in _dictionary.Values) { - Register(pluginContext); + foreach (var appDict in pluginDict.Where(x => x.Key == applicationContext).Select(x => x.Value)) + { + foreach (var eventHandlerItem in appDict.Values.SelectMany(x => x)) + { + OnRemoveEventHandler(eventHandlerItem.EventHandlerContext); + eventHandlerItem.Dispose(); + } + } + + pluginDict.Remove(applicationContext); } } /// - /// Assign existing event to the module. + /// Raises the AddEventHandler event. + /// + /// The event handler context. + private void OnAddEventHandler(IEventHandlerContext eventHandlerContext) + { + AddEventHandler?.Invoke(this, eventHandlerContext); + } + + /// + /// Raises the RemoveEventHandler event. /// - /// The context of the module. - private void AssignToModule(IModuleContext moduleContext) + /// The event handler context. + private void OnRemoveEventHandler(IEventHandlerContext eventHandlerContext) { - //foreach (var scheduleItem in Dictionary.Values.SelectMany(x => x)) - //{ - // if (scheduleItem.moduleId.Equals(moduleContext?.ModuleId)) - // { - // scheduleItem.AddModule(moduleContext); - // } - //} + RemoveEventHandler?.Invoke(this, eventHandlerContext); } /// - /// Remove an existing modules to the event. + /// Raises the event when an plugin is added. /// - /// The context of the module. - private void DetachFromModule(IModuleContext moduleContext) + /// The source of the event. + /// The context of the plugin being added. + private void OnAddPlugin(object sender, IPluginContext e) { - //foreach (var scheduleItem in Dictionary.Values.SelectMany(x => x)) - //{ - // if (scheduleItem.moduleId.Equals(moduleContext?.ModuleId)) - // { - // scheduleItem.DetachModule(moduleContext); - // } - //} + Register(e); + } + + /// + /// Raises the event when a plugin is removed. + /// + /// The source of the event. + /// The context of the plugin being removed. + private void OnRemovePlugin(object sender, IPluginContext e) + { + Remove(e); + } + + /// + /// Raises the event when an application is added. + /// + /// The source of the event. + /// The context of the application being added. + private void OnAddApplication(object sender, IApplicationContext e) + { + Register(e); + } + + /// + /// Raises the event when an application is removed. + /// + /// The source of the event. + /// The context of the application being removed. + private void OnRemoveApplication(object sender, IApplicationContext e) + { + Remove(e); } /// - /// Removes all jobs associated with the specified plugin context. + /// Information about the component is collected and prepared for output in the log. /// - /// The context of the plugin that contains the event to remove. - public void Remove(IPluginContext pluginContext) + private void Log() { - //// the plugin has not been registered in the manager - //if (!Dictionary.ContainsKey(pluginContext)) - //{ - // return; - //} + if (!EventHandlers.Any()) + { + return; + } - //foreach (var scheduleItem in Dictionary[pluginContext]) - //{ - // scheduleItem.Dispose(); - //} + using var frame = new LogFrameSimple(_httpServerContext.Log); + var list = new List + { + I18N.Translate("webexpress.webcore:eventmanager.titel") + }; - //Dictionary.Remove(pluginContext); + foreach (var eventHandlerContext in EventHandlers) + { + list.Add + ( + I18N.Translate("webexpress.webcore:eventmanager.handler", eventHandlerContext.EventId, eventHandlerContext.ApplicationContext?.ApplicationId) + ); + } + + _httpServerContext.Log.Info(string.Join(Environment.NewLine, list)); } /// - /// Information about the component is collected and prepared for output in the event. + /// Release of unmanaged resources reserved during use. /// - /// The context of the plugin. - /// A list of log entries. - /// The shaft deep. - public void PrepareForLog(IPluginContext pluginContext, IList output, int deep) + public void Dispose() { - //foreach (var scheduleItem in GetScheduleItems(pluginContext)) - //{ - // output.Add - // ( - // string.Empty.PadRight(deep) + - // InternationalizationManager.I18N - // ( - // "webexpress:eventmanager.job", - // scheduleItem.JobId, - // scheduleItem.ModuleContext - // ) - // ); - //} + _componentHub.PluginManager.AddPlugin -= OnAddPlugin; + _componentHub.PluginManager.RemovePlugin -= OnRemovePlugin; + _componentHub.ApplicationManager.AddApplication -= OnAddApplication; + _componentHub.ApplicationManager.RemoveApplication -= OnRemoveApplication; + + GC.SuppressFinalize(this); } } } diff --git a/src/WebExpress.WebCore/WebEvent/IEvent.cs b/src/WebExpress.WebCore/WebEvent/IEvent.cs new file mode 100644 index 0000000..26c48e4 --- /dev/null +++ b/src/WebExpress.WebCore/WebEvent/IEvent.cs @@ -0,0 +1,9 @@ +namespace WebExpress.WebCore.WebEvent +{ + /// + /// Represents an event. + /// + public interface IEvent + { + } +} diff --git a/src/WebExpress.WebCore/WebEvent/IEventArgument.cs b/src/WebExpress.WebCore/WebEvent/IEventArgument.cs new file mode 100644 index 0000000..bebc6a5 --- /dev/null +++ b/src/WebExpress.WebCore/WebEvent/IEventArgument.cs @@ -0,0 +1,9 @@ +namespace WebExpress.WebCore.WebEvent +{ + /// + /// Represents an argument for an event. + /// + public interface IEventArgument + { + } +} \ No newline at end of file diff --git a/src/WebExpress.WebCore/WebEvent/IEventContext.cs b/src/WebExpress.WebCore/WebEvent/IEventContext.cs deleted file mode 100644 index 72f9f67..0000000 --- a/src/WebExpress.WebCore/WebEvent/IEventContext.cs +++ /dev/null @@ -1,18 +0,0 @@ -using WebExpress.WebCore.WebModule; -using WebExpress.WebCore.WebPlugin; - -namespace WebExpress.WebCore.WebEvent -{ - public interface IEventContext - { - /// - /// Returns the associated plugin context. - /// - IPluginContext PluginContext { get; } - - /// - /// Returns the corresponding module context. - /// - IModuleContext ModuleContext { get; } - } -} diff --git a/src/WebExpress.WebCore/WebEvent/IEventHandler.cs b/src/WebExpress.WebCore/WebEvent/IEventHandler.cs index 8dd298f..11b51ad 100644 --- a/src/WebExpress.WebCore/WebEvent/IEventHandler.cs +++ b/src/WebExpress.WebCore/WebEvent/IEventHandler.cs @@ -1,6 +1,30 @@ -namespace WebExpress.WebCore.WebEvent +using WebExpress.WebCore.WebComponent; + +namespace WebExpress.WebCore.WebEvent { - public interface IEventHandler + /// + /// Represents an event handler. + /// + public interface IEventHandler : IComponent { + /// + /// Process the event. + /// + /// The object that triggered the event. + /// The argument for the event. + void Process(object sender, IEventArgument eventArgument); + } + + /// + /// Represents an event handler. + /// + public interface IEventHandler : IComponent where T : class, IEventArgument + { + /// + /// Process the event. + /// + /// The object that triggered the event. + /// The argument for the event. + void Process(object sender, T eventArgument); } } diff --git a/src/WebExpress.WebCore/WebEvent/IEventHandlerContext.cs b/src/WebExpress.WebCore/WebEvent/IEventHandlerContext.cs new file mode 100644 index 0000000..8f86628 --- /dev/null +++ b/src/WebExpress.WebCore/WebEvent/IEventHandlerContext.cs @@ -0,0 +1,32 @@ +using WebExpress.WebCore.WebApplication; +using WebExpress.WebCore.WebComponent; +using WebExpress.WebCore.WebPlugin; + +namespace WebExpress.WebCore.WebEvent +{ + /// + /// Represents the context of an event. + /// + public interface IEventHandlerContext : IContext + { + /// + /// Returns the event id. + /// + IComponentId EventId { get; } + + /// + /// Returns the event handler id. + /// + string EventHandlerId { get; } + + /// + /// Returns the associated plugin context. + /// + IPluginContext PluginContext { get; } + + /// + /// Returns the corresponding application context. + /// + IApplicationContext ApplicationContext { get; } + } +} diff --git a/src/WebExpress.WebCore/WebEvent/IEventManager.cs b/src/WebExpress.WebCore/WebEvent/IEventManager.cs new file mode 100644 index 0000000..d725df2 --- /dev/null +++ b/src/WebExpress.WebCore/WebEvent/IEventManager.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using WebExpress.WebCore.WebApplication; +using WebExpress.WebCore.WebComponent; + +namespace WebExpress.WebCore.WebEvent +{ + /// + /// Represents the interface for managing web events. + /// + public interface IEventManager : IComponentManager + { + /// + /// Returns the collection of events. + /// + IEnumerable EventHandlers { get; } + + /// + /// Returns the event handler contexts. + /// + /// The type of event. + /// The application context. + /// An IEnumerable of event handler contexts. + IEnumerable GetEventHandlers(IApplicationContext applicationContext) + where TEvent : IEvent; + + /// + /// Returns the event handler contexts. + /// + /// The application context. + /// The type of event. + /// An IEnumerable of event handler contexts. + IEnumerable GetEventHandlers(IApplicationContext applicationContext, Type eventType); + + /// + /// Raises the specified event. + /// + /// The type of event. + /// The application context. + /// The sender object. + /// The event argument. + void RaiseEvent(IApplicationContext applicationContext, object sender, IEventArgument argument) + where TEvent : IEvent; + } +} diff --git a/src/WebExpress.WebCore/WebEvent/Model/EventDictionary.cs b/src/WebExpress.WebCore/WebEvent/Model/EventDictionary.cs new file mode 100644 index 0000000..49c2412 --- /dev/null +++ b/src/WebExpress.WebCore/WebEvent/Model/EventDictionary.cs @@ -0,0 +1,151 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using WebExpress.WebCore.WebApplication; +using WebExpress.WebCore.WebPlugin; + +namespace WebExpress.WebCore.WebEvent.Model +{ + /// + /// Represents a dictionary that provides a mapping from plugin contexts to dictionaries of application contexts and event handler. + /// + internal class EventDictionary : Dictionary>>> + { + /// + /// Adds a event item to the dictionary. + /// + /// The plugin context. + /// The application context. + /// The event item. + /// True if the event handler item was added successfully, false if an element with the same status code already exists. + public bool AddEventItem(IPluginContext pluginContext, IApplicationContext applicationContext, EventItem eventItem) + { + var type = eventItem.EventClass; + + if (!typeof(IEvent).IsAssignableFrom(type)) + { + return false; + } + + if (!TryGetValue(pluginContext, out var appContextDict)) + { + appContextDict = []; + this[pluginContext] = appContextDict; + } + + if (!appContextDict.TryGetValue(applicationContext, out var eventDict)) + { + eventDict = []; + appContextDict[applicationContext] = eventDict; + } + + if (!eventDict.TryGetValue(type, out var eventList)) + { + eventList = []; + eventDict[type] = eventList; + } + + if (eventList.Any(x => x.EventClass == type)) + { + return false; // item with the same event handler already exists + } + + eventList.Add(eventItem); + + return true; + } + + /// + /// Removes a event from the dictionary. + /// + /// The plugin context. + /// The application context. + public void RemoveEventHandler(IPluginContext pluginContext, IApplicationContext applicationContext) + where TEvent : IEvent + { + var type = typeof(TEvent); + + if (ContainsKey(pluginContext)) + { + var appContextDict = this[pluginContext]; + + if (appContextDict.ContainsKey(applicationContext)) + { + var eventDict = appContextDict[applicationContext]; + + if (eventDict.ContainsKey(type)) + { + eventDict.Remove(type); + + if (eventDict.Count == 0) + { + appContextDict.Remove(applicationContext); + + if (appContextDict.Count == 0) + { + Remove(pluginContext); + } + } + } + } + } + } + + /// + /// Returns the event handler from the dictionary. + /// + /// The type of event. + /// The application context. + /// An IEnumerable of event items + public IEnumerable GetEventHandlerItems(IApplicationContext applicationContext) + where TEvent : IEvent + { + return GetEventHandlerItems(applicationContext, typeof(TEvent)); + } + + /// + /// Returns the event handler from the dictionary. + /// + /// The application context. + /// The type of event. + /// An IEnumerable of event items + public IEnumerable GetEventHandlerItems(IApplicationContext applicationContext, Type eventType) + { + if (!typeof(IEvent).IsAssignableFrom(eventType)) + { + return []; + } + + if (ContainsKey(applicationContext?.PluginContext)) + { + var appContextDict = this[applicationContext?.PluginContext]; + + if (appContextDict.ContainsKey(applicationContext)) + { + var eventDict = appContextDict[applicationContext]; + + if (eventDict.ContainsKey(eventType)) + { + return eventDict[eventType]; + } + } + } + + return []; + } + + /// + /// Returns all event handler contexts for a given plugin context. + /// + /// The plugin context. + /// An IEnumerable of event handler contexts. + public IEnumerable GetEventHandlers(IPluginContext pluginContext) + { + return this.Where(entry => entry.Key == pluginContext) + .SelectMany(entry => entry.Value.Values) + .SelectMany(dict => dict.Values) + .SelectMany(x => x) + .Select(x => x.EventHandlerContext); + } + } +} diff --git a/src/WebExpress.WebCore/WebEvent/Model/EventItem.cs b/src/WebExpress.WebCore/WebEvent/Model/EventItem.cs new file mode 100644 index 0000000..16c78f1 --- /dev/null +++ b/src/WebExpress.WebCore/WebEvent/Model/EventItem.cs @@ -0,0 +1,128 @@ +using System; +using System.Linq; +using WebExpress.WebCore.WebApplication; +using WebExpress.WebCore.WebComponent; +using WebExpress.WebCore.WebPlugin; + +namespace WebExpress.WebCore.WebEvent.Model +{ + /// + /// Represents an event item. + /// This class is intended for internal use only. + /// + internal class EventItem : IDisposable + { + private readonly IComponentHub _componentHub; + private readonly IComponent _instance; + + /// + /// Returns the associated plugin context. + /// + public IPluginContext PluginContext { get; private set; } + + /// + /// Returns the corresponding application context. + /// + public IApplicationContext ApplicationContext { get; private set; } + + /// + /// Returns or sets the event handler context. + /// + public IEventHandlerContext EventHandlerContext { get; private set; } + + /// + /// Returns or sets the event class. + /// + public Type EventClass { get; private set; } + + /// + /// Initializes a new instance of the class. + /// + /// The associated component hub. + /// The reference to the context of the host. + /// The associated plugin context. + /// The corresponding application context. + /// The event handler context. + /// The event handler type. + /// The event class. + public EventItem(IComponentHub componentHub, IHttpServerContext httpServerContext, IPluginContext pluginContext, IApplicationContext applicationContext, IEventHandlerContext eventHandlerContext, Type eventHandlerType, Type eventClass) + { + _componentHub = componentHub; + PluginContext = pluginContext; + ApplicationContext = applicationContext; + EventHandlerContext = eventHandlerContext; + EventClass = eventClass; + + if (typeof(IEventHandler).IsAssignableFrom(eventHandlerType)) + { + _instance = ComponentActivator.CreateInstance + ( + eventHandlerType, + eventHandlerContext, + httpServerContext, + _componentHub, + pluginContext, + applicationContext + ); + + return; + } + + _instance = ComponentActivator.CreateInstance + ( + eventHandlerType, + eventHandlerContext, + httpServerContext, + _componentHub, + pluginContext, + applicationContext + ); + } + + /// + /// Process the event. + /// + /// The object that triggered the event. + /// The argument for the event. + public void Process(object sender, IEventArgument eventArgument) + { + if (_instance is IEventHandler handler) + { + handler.Process(sender, eventArgument); + + return; + } + + var handlerType = _instance.GetType() + .GetInterfaces() + .FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IEventHandler<>)); + + if (handlerType != null) + { + var genericArgument = handlerType.GetGenericArguments().First(); + var method = handlerType.GetMethod("Process"); + method.Invoke(_instance, [sender, eventArgument]); + } + } + + /// + /// Performs application-specific tasks related to sharing, returning, or resetting unmanaged resources. + /// + public void Dispose() + { + if (_instance is IDisposable disposable) + { + disposable.Dispose(); + } + } + + /// + /// Convert the resource element to a string. + /// + /// The event element in its string representation. + public override string ToString() + { + return $"Event: '{EventClass.FullName.ToLower()}'"; + } + } +} diff --git a/src/WebExpress.WebCore/WebEx.cs b/src/WebExpress.WebCore/WebEx.cs index cbf3106..5a7f82c 100644 --- a/src/WebExpress.WebCore/WebEx.cs +++ b/src/WebExpress.WebCore/WebEx.cs @@ -8,43 +8,36 @@ using WebExpress.WebCore.Config; using WebExpress.WebCore.Internationalization; using WebExpress.WebCore.WebComponent; -using WebExpress.WebCore.WebUri; +using WebExpress.WebCore.WebEndpoint; +using WebExpress.WebCore.WebLog; +using WebExpress.WebCore.WebPackage; [assembly: InternalsVisibleTo("WebExpress.WebCore.Test")] namespace WebExpress.WebCore { - public class WebEx + /// + /// The class provides a web server application for WebExpress. + /// + public sealed class WebEx { + private static IComponentHub _componentHub; + private HttpServer _httpServer; + /// /// Returns or sets the name of the web server. /// public string Name { get; set; } = "WebExpress"; - /// - /// The http(s) server. - /// - private HttpServer HttpServer { get; set; } - /// /// Returns the program version. /// public static string Version => Assembly.GetExecutingAssembly().GetName().Version.ToString(); /// - /// Entry point of application. + /// Returns the component hub. /// - /// Call arguments. - /// The return code. 0 on success. A number greater than 0 for errors. - public static int Main(string[] args) - { - var app = new WebEx() - { - Name = Assembly.GetExecutingAssembly().GetName().Name - }; - - return app.Execution(args); - } + public static IComponentHub ComponentHub => _componentHub; /// /// Running the application. @@ -103,7 +96,7 @@ public int Execution(string[] args) return 1; } - WebPackage.PackageBuilder.Create(argumentDict["spec"], argumentDict["config"], argumentDict["target"], argumentDict["output"]); + PackageBuilder.Create(argumentDict["spec"], argumentDict["config"], argumentDict["target"], argumentDict["output"]); return 0; } @@ -126,7 +119,7 @@ public int Execution(string[] args) Initialization(ArgumentParser.Current.GetValidArguments(args), Path.Combine(Path.Combine(Environment.CurrentDirectory, "config"), argumentDict["config"])); // start the manager - ComponentManager.Execute(); + (_componentHub as ComponentHub).Execute(); // starting the web server Start(); @@ -151,7 +144,7 @@ private void OnCancel(object sender, ConsoleCancelEventArgs e) /// Initialization /// /// The valid arguments. - /// The configuration file. + /// The configuration file. private void Initialization(string args, string configFile) { // Config laden @@ -190,46 +183,48 @@ private void Initialization(string args, string configFile) var context = new HttpServerContext ( - config.Uri, + new RouteEndpoint(config.Route), config.Endpoints, Path.GetFullPath(packageBase), Path.GetFullPath(assetBase), Path.GetFullPath(dataBase), Path.GetDirectoryName(configFile), - new UriResource(config.ContextPath), + new RouteEndpoint(config.ContextPath), culture, log, null ); - HttpServer = new HttpServer(context) + _httpServer = new HttpServer(context) { Config = config }; + _componentHub = ComponentActivator.CreateInstance(_httpServer.HttpServerContext); + // start logging - HttpServer.HttpServerContext.Log.Begin(config.Log); + _httpServer.HttpServerContext.Log.Begin(config.Log); // log program start - HttpServer.HttpServerContext.Log.Seperator('/'); - HttpServer.HttpServerContext.Log.Info(message: InternationalizationManager.I18N("webexpress:app.startup")); - HttpServer.HttpServerContext.Log.Info(message: "".PadRight(80, '-')); - HttpServer.HttpServerContext.Log.Info(message: InternationalizationManager.I18N("webexpress:app.version"), args: Version); - HttpServer.HttpServerContext.Log.Info(message: InternationalizationManager.I18N("webexpress:app.arguments"), args: args); - HttpServer.HttpServerContext.Log.Info(message: InternationalizationManager.I18N("webexpress:app.workingdirectory"), args: Environment.CurrentDirectory); - HttpServer.HttpServerContext.Log.Info(message: InternationalizationManager.I18N("webexpress:app.packagebase"), args: config.PackageBase); - HttpServer.HttpServerContext.Log.Info(message: InternationalizationManager.I18N("webexpress:app.assetbase"), args: config.AssetBase); - HttpServer.HttpServerContext.Log.Info(message: InternationalizationManager.I18N("webexpress:app.database"), args: config.DataBase); - HttpServer.HttpServerContext.Log.Info(message: InternationalizationManager.I18N("webexpress:app.configurationdirectory"), args: Path.GetDirectoryName(configFile)); - HttpServer.HttpServerContext.Log.Info(message: InternationalizationManager.I18N("webexpress:app.configuration"), args: Path.GetFileName(configFile)); - HttpServer.HttpServerContext.Log.Info(message: InternationalizationManager.I18N("webexpress:app.logdirectory"), args: Path.GetDirectoryName(HttpServer.HttpServerContext.Log.Filename)); - HttpServer.HttpServerContext.Log.Info(message: InternationalizationManager.I18N("webexpress:app.log"), args: Path.GetFileName(HttpServer.HttpServerContext.Log.Filename)); + _httpServer.HttpServerContext.Log.Seperator('/'); + _httpServer.HttpServerContext.Log.Info(message: I18N.Translate("webexpress.webcore:app.startup")); + _httpServer.HttpServerContext.Log.Info(message: "".PadRight(80, '-')); + _httpServer.HttpServerContext.Log.Info(message: I18N.Translate("webexpress.webcore:app.version"), args: Version); + _httpServer.HttpServerContext.Log.Info(message: I18N.Translate("webexpress.webcore:app.arguments"), args: args); + _httpServer.HttpServerContext.Log.Info(message: I18N.Translate("webexpress.webcore:app.workingdirectory"), args: Environment.CurrentDirectory); + _httpServer.HttpServerContext.Log.Info(message: I18N.Translate("webexpress.webcore:app.packagebase"), args: config.PackageBase); + _httpServer.HttpServerContext.Log.Info(message: I18N.Translate("webexpress.webcore:app.assetbase"), args: config.AssetBase); + _httpServer.HttpServerContext.Log.Info(message: I18N.Translate("webexpress.webcore:app.database"), args: config.DataBase); + _httpServer.HttpServerContext.Log.Info(message: I18N.Translate("webexpress.webcore:app.configurationdirectory"), args: Path.GetDirectoryName(configFile)); + _httpServer.HttpServerContext.Log.Info(message: I18N.Translate("webexpress.webcore:app.configuration"), args: Path.GetFileName(configFile)); + _httpServer.HttpServerContext.Log.Info(message: I18N.Translate("webexpress.webcore:app.logdirectory"), args: Path.GetDirectoryName(_httpServer.HttpServerContext.Log.Filename)); + _httpServer.HttpServerContext.Log.Info(message: I18N.Translate("webexpress.webcore:app.log"), args: Path.GetFileName(_httpServer.HttpServerContext.Log.Filename)); foreach (var v in config.Endpoints) { - HttpServer.HttpServerContext.Log.Info(message: InternationalizationManager.I18N("webexpress:app.uri"), args: v.Uri); + _httpServer.HttpServerContext.Log.Info(message: I18N.Translate("webexpress.webcore:app.uri"), args: v.Uri); } - HttpServer.HttpServerContext.Log.Seperator('='); + _httpServer.HttpServerContext.Log.Seperator('='); if (!Directory.Exists(config.PackageBase)) { @@ -254,7 +249,7 @@ private void Initialization(string args, string configFile) /// private void Start() { - HttpServer.Start(); + _httpServer.Start(); Thread.CurrentThread.Join(); } @@ -264,17 +259,40 @@ private void Start() /// private void Exit() { - HttpServer.Stop(); + _httpServer.Stop(); // end of program log - HttpServer.HttpServerContext.Log.Seperator('='); - HttpServer.HttpServerContext.Log.Info(message: InternationalizationManager.I18N("webexpress:app.errors"), args: HttpServer.HttpServerContext.Log.ErrorCount); - HttpServer.HttpServerContext.Log.Info(message: InternationalizationManager.I18N("webexpress:app.warnings"), args: HttpServer.HttpServerContext.Log.WarningCount); - HttpServer.HttpServerContext.Log.Info(message: InternationalizationManager.I18N("webexpress:app.done")); - HttpServer.HttpServerContext.Log.Seperator('/'); + _httpServer.HttpServerContext.Log.Seperator('='); + _httpServer.HttpServerContext.Log.Info(message: I18N.Translate("webexpress.webcore:app.errors"), args: _httpServer.HttpServerContext.Log.ErrorCount); + _httpServer.HttpServerContext.Log.Info(message: I18N.Translate("webexpress.webcore:app.warnings"), args: _httpServer.HttpServerContext.Log.WarningCount); + _httpServer.HttpServerContext.Log.Info(message: I18N.Translate("webexpress.webcore:app.done")); + _httpServer.HttpServerContext.Log.Seperator('/'); + + // Stop running + (_componentHub as ComponentHub).ShutDown(); // stop logging - HttpServer.HttpServerContext.Log.Close(); + _httpServer.HttpServerContext.Log.Close(); + } + + /// + /// Returns a component based on its id. + /// + /// The id. + /// The instance of the component or null. + public static IComponentManager GetComponent(string id) + { + return _componentHub.GetComponentManager(id); + } + + /// + /// Returns a component based on its type. + /// + /// The component class. + /// The instance of the component or null. + public static T GetComponent() where T : IComponentManager + { + return _componentHub.GetComponentManager(); } } } diff --git a/src/WebExpress.WebCore/WebExpress.WebCore.csproj b/src/WebExpress.WebCore/WebExpress.WebCore.csproj index 52fc48c..95844d4 100644 --- a/src/WebExpress.WebCore/WebExpress.WebCore.csproj +++ b/src/WebExpress.WebCore/WebExpress.WebCore.csproj @@ -3,9 +3,9 @@ Library WebExpress.WebCore - 0.0.7.0 - 0.0.7.0 - net8.0 + 0.0.8.0 + 0.0.8.0 + net9.0 any https://github.com/ReneSchwarzer/WebExpress.git Rene_Schwarzer@hotmail.de @@ -14,7 +14,7 @@ true True Core library of the WebExpress web server. - 0.0.7-alpha + 0.0.8-alpha https://github.com/ReneSchwarzer/WebExpress icon.png README.md @@ -22,6 +22,7 @@ webexpress True true + true @@ -36,6 +37,7 @@ + diff --git a/src/WebExpress.WebCore/WebFragment/FragmentComparer.cs b/src/WebExpress.WebCore/WebFragment/FragmentComparer.cs new file mode 100644 index 0000000..2d627ff --- /dev/null +++ b/src/WebExpress.WebCore/WebFragment/FragmentComparer.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; + +namespace WebExpress.WebCore.WebFragment +{ + /// + /// Provides a method to compare two objects of type T for equality. + /// + /// The type of objects to compare. + [Obsolete("FragmentComparer is obsolete. Use a different comparer implementation.")] + public class FragmentComparer : IEqualityComparer + { + /// + /// Tests for equality. + /// + /// The first object to compare. + /// The second object to compare. + /// True if both objects are similar; false otherwise. + public bool Equals(T x, T y) + { + if (x == null && y == null) + { + return true; + } + else if (x.GetType() == y.GetType()) + { + return true; + } + + return false; + } + + /// + /// Returns a hash code for the specified object. + /// + /// The object for which to return a hash code. + /// The hash code. + public int GetHashCode([DisallowNull] T obj) + { + return obj.GetType().GetHashCode(); + } + } +} diff --git a/src/WebExpress.WebCore/WebFragment/FragmentConditionExtentsion.cs b/src/WebExpress.WebCore/WebFragment/FragmentConditionExtentsion.cs new file mode 100644 index 0000000..e8085db --- /dev/null +++ b/src/WebExpress.WebCore/WebFragment/FragmentConditionExtentsion.cs @@ -0,0 +1,31 @@ +using System.Collections.Generic; +using WebExpress.WebCore.WebCondition; +using WebExpress.WebCore.WebMessage; + +namespace WebExpress.WebCore.WebFragment +{ + /// + /// Provides extension methods for checking conditions. + /// + public static class FragmentConditionExtentsion + { + /// + /// Checks if all conditions in the collection are fulfilled for the given request. + /// + /// The collection of conditions to check. + /// The request to evaluate the conditions against. + /// True if all conditions are fulfilled; otherwise, false. + public static bool Check(this IEnumerable conditions, Request request) + { + foreach (var condition in conditions) + { + if (!condition.Fulfillment(request)) + { + return false; + } + } + + return true; + } + } +} diff --git a/src/WebExpress.WebCore/WebFragment/FragmentContext.cs b/src/WebExpress.WebCore/WebFragment/FragmentContext.cs new file mode 100644 index 0000000..9994ff5 --- /dev/null +++ b/src/WebExpress.WebCore/WebFragment/FragmentContext.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; +using WebExpress.WebCore.WebApplication; +using WebExpress.WebCore.WebComponent; +using WebExpress.WebCore.WebCondition; +using WebExpress.WebCore.WebPlugin; + +namespace WebExpress.WebCore.WebFragment +{ + /// + /// Represents the context for a web fragment, including plugin context, application context, + /// conditions for activation, culture information, and caching behavior. + /// + public class FragmentContext : IFragmentContext + { + /// + /// Returns the context of the associated plugin. + /// + public IPluginContext PluginContext { get; internal set; } + + /// + /// Returns the application context. + /// + public IApplicationContext ApplicationContext { get; internal set; } + + /// + /// Gets the unique identifier for the fragment. + /// + public IComponentId FragmentId { get; internal set; } + + /// + /// Returns the conditions that must be met for the component to be active. + /// + public IEnumerable Conditions { get; internal set; } = []; + + /// + /// Determines whether the component is created once and reused on each execution. + /// + public bool Cache { get; internal set; } + + /// + /// Returns the section. + /// + public Type Section { get; internal set; } + + /// + /// Returns the scope. + /// + public Type Scope { get; internal set; } + + /// + /// Initializes a new instance of the class. + /// + public FragmentContext() + { + } + + /// + /// Returns a string that represents the current object. + /// + /// A string that represents the current object. + public override string ToString() + { + return $"Fragment: {FragmentId}"; + } + } +} diff --git a/src/WebExpress.WebCore/WebFragment/FragmentManager.cs b/src/WebExpress.WebCore/WebFragment/FragmentManager.cs new file mode 100644 index 0000000..e509279 --- /dev/null +++ b/src/WebExpress.WebCore/WebFragment/FragmentManager.cs @@ -0,0 +1,498 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using WebExpress.WebCore.Internationalization; +using WebExpress.WebCore.WebApplication; +using WebExpress.WebCore.WebAttribute; +using WebExpress.WebCore.WebComponent; +using WebExpress.WebCore.WebCondition; +using WebExpress.WebCore.WebFragment.Model; +using WebExpress.WebCore.WebHtml; +using WebExpress.WebCore.WebLog; +using WebExpress.WebCore.WebPage; +using WebExpress.WebCore.WebPlugin; +using WebExpress.WebCore.WebScope; +using WebExpress.WebCore.WebSection; + +namespace WebExpress.WebCore.WebFragment +{ + /// + /// The fragment manager. Fragments are independent parts of a page. + /// + public sealed class FragmentManager : IFragmentManager + { + private readonly IComponentHub _componentHub; + private readonly IHttpServerContext _httpServerContext; + private readonly FragmentDictionary _dictionary = new(); + + /// + /// An event that fires when an fragment is added. + /// + public event EventHandler AddFragment; + + /// + /// An event that fires when an fragment is removed. + /// + public event EventHandler RemoveFragment; + + /// + /// Returns the collection of fragment contexts. + /// + public IEnumerable Fragments => _dictionary.All; + + /// + /// Initializes a new instance of the class. + /// + /// The component hub. + /// The reference to the context of the host. + [SuppressMessage("CodeQuality", "IDE0051:Remove unused private members", Justification = "Used via Reflection.")] + private FragmentManager(IComponentHub componentHub, IHttpServerContext httpServerContext) + { + _componentHub = componentHub; + _httpServerContext = httpServerContext; + + _componentHub.PluginManager.AddPlugin += OnAddPlugin; + _componentHub.PluginManager.RemovePlugin += OnRemovePlugin; + _componentHub.ApplicationManager.AddApplication += OnAddApplication; + _componentHub.ApplicationManager.RemoveApplication += OnRemoveApplication; + + _httpServerContext.Log.Debug + ( + I18N.Translate("webexpress.webcore:fragmentmanager.initialization") + ); + } + + /// + /// Discovers and binds fragments to an application. + /// + /// The context of the plugin whose fragments are to be associated. + private void Register(IPluginContext pluginContext) + { + if (_dictionary.Contains(pluginContext)) + { + return; + } + + Register(pluginContext, _componentHub.ApplicationManager.GetApplications(pluginContext)); + } + + /// + /// Discovers and binds fragments to an application. + /// + /// The context of the application whose fragments are to be associated. + private void Register(IApplicationContext applicationContext) + { + foreach (var pluginContext in _componentHub.PluginManager.GetPlugins(applicationContext)) + { + if (_dictionary.Contains(pluginContext, applicationContext)) + { + continue; + } + + Register(pluginContext, [applicationContext]); + } + } + + /// + /// Registers pages for a given plugin and application context. + /// + /// The plugin context. + /// The application context (optional). + private void Register(IPluginContext pluginContext, IEnumerable applicationContexts) + { + var assembly = pluginContext.Assembly; + + foreach (var fragmentType in assembly.GetTypes() + .Where(x => x.IsClass == true && x.IsSealed && x.IsPublic) + .Where(x => x.GetInterface(typeof(IFragment<,>).Name) != null)) + { + var id = fragmentType.FullName?.ToLower(); + var scopes = new List(); + var sections = new List(); + var conditions = new List(); + var cache = false; + var order = 0; + + // determining attributes + foreach (var customAttribute in fragmentType.CustomAttributes.Where + ( + x => x.AttributeType.GetInterfaces() + .Contains(typeof(IEndpointAttribute)) + )) + { + if (customAttribute.AttributeType.Name == typeof(ScopeAttribute<>).Name && customAttribute.AttributeType.Namespace == typeof(ScopeAttribute<>).Namespace) + { + scopes.Add(customAttribute.AttributeType.GenericTypeArguments.FirstOrDefault()); + } + else if (customAttribute.AttributeType.Name == typeof(ConditionAttribute<>).Name && customAttribute.AttributeType.Namespace == typeof(ConditionAttribute<>).Namespace) + { + var condition = customAttribute.AttributeType.GenericTypeArguments.FirstOrDefault(); + conditions.Add(Activator.CreateInstance(condition) as ICondition); + } + else if (customAttribute.AttributeType == typeof(CacheAttribute)) + { + cache = true; + } + } + + foreach (var customAttribute in fragmentType.CustomAttributes.Where + ( + x => x.AttributeType.GetInterfaces().Contains(typeof(IFragmentAttribute)) + )) + { + if (customAttribute.AttributeType.Name == typeof(SectionAttribute<>).Name && customAttribute.AttributeType.Namespace == typeof(SectionAttribute<>).Namespace) + { + sections.Add(customAttribute.AttributeType.GenericTypeArguments.FirstOrDefault()); + } + else if (customAttribute.AttributeType == typeof(OrderAttribute)) + { + try + { + order = Convert.ToInt32(customAttribute.ConstructorArguments.FirstOrDefault().Value); + } + catch + { + } + } + } + + // check section + if (sections.Count == 0) + { + _httpServerContext.Log.Warning(I18N.Translate + ( + "webexpress.webcore:fragmentmanager.error.section" + )); + + continue; + } + + // check scope + if (scopes.Count == 0) + { + scopes.Add(typeof(IScope)); + } + + // assign the fragment to existing applications + foreach (var applicationContext in _componentHub.ApplicationManager.GetApplications(pluginContext)) + { + // assign section + foreach (var section in sections) + { + // assign scope + foreach (var scope in scopes) + { + var fragmentContext = new FragmentContext() + { + PluginContext = pluginContext, + ApplicationContext = applicationContext, + FragmentId = new ComponentId(id), + Cache = cache, + Section = section, + Scope = scope, + Conditions = conditions + }; + + var fragmentItem = new FragmentItem(_componentHub, _httpServerContext) + { + PluginContext = pluginContext, + ApplicationContext = applicationContext, + FragmentContext = fragmentContext, + FragmentClass = fragmentType, + Order = order, + Cache = cache, + Conditions = conditions, + Section = section, + Scope = scope + }; + + if (_dictionary.AddFragmentItem(pluginContext, applicationContext, fragmentItem)) + { + OnAddFragment(fragmentContext); + + _httpServerContext?.Log.Debug + ( + I18N.Translate + ( + "webexpress.webcore:fragmentmanager.register", + id, + section, + applicationContext.ApplicationId + ) + ); + } + } + } + } + } + + Log(); + } + + /// + /// Removes all components associated with the specified plugin context. + /// + /// The context of the plugin that contains the components to remove. + internal void Remove(IPluginContext pluginContext) + { + if (pluginContext == null) + { + return; + } + + var fragments = _dictionary.RemoveFragments(pluginContext); + + foreach (var fragment in fragments) + { + OnRemoveFragment(fragment); + } + } + + /// + /// Removes all fragments associated with the specified application context. + /// + /// The context of the application that contains the fragments to remove. + internal void Remove(IApplicationContext applicationContext) + { + if (applicationContext == null) + { + return; + } + + var fragments = _dictionary.RemoveFragments(applicationContext); + + foreach (var fragment in fragments) + { + OnRemoveFragment(fragment); + } + } + + /// + /// Raises the AddFragment event. + /// + /// The fragment context. + private void OnAddFragment(IFragmentContext fragmentContext) + { + AddFragment?.Invoke(this, fragmentContext); + } + + /// + /// Raises the RemoveFragment event. + /// + /// The fragment context. + private void OnRemoveFragment(IFragmentContext fragmentContext) + { + RemoveFragment?.Invoke(this, fragmentContext); + } + + /// + /// Raises the event when an plugin is added. + /// + /// The source of the event. + /// The context of the plugin being added. + private void OnAddPlugin(object sender, IPluginContext e) + { + Register(e); + } + + /// + /// Raises the event when a plugin is removed. + /// + /// The source of the event. + /// The context of the plugin being removed. + private void OnRemovePlugin(object sender, IPluginContext e) + { + Remove(e); + } + + /// + /// Raises the event when an application is added. + /// + /// The source of the event. + /// The context of the application being added. + private void OnAddApplication(object sender, IApplicationContext e) + { + Register(e); + } + + /// + /// Raises the event when an application is removed. + /// + /// The source of the event. + /// The context of the application being removed. + private void OnRemoveApplication(object sender, IApplicationContext e) + { + Remove(e); + } + + /// + /// Returns all fragment contexts that belong to a given fragment type. + /// + /// The fragment type. + /// An enumeration of the filtered fragment contexts. + public IEnumerable GetFragments() where T : IFragmentBase + { + return GetFragments(typeof(T)); + } + + /// + /// Returns all fragment contexts that belong to a given fragment type. + /// + /// The fragment type. + /// An enumeration of the filtered fragment contexts. + public IEnumerable GetFragments(Type fragmentType) + { + return _dictionary.GetFragments(fragmentType); + } + + /// + /// Returns all fragment contexts that belong to a given fragment type. + /// + /// The fragment type. + /// The application context. + /// An enumeration of the filtered fragment contexts. + public IEnumerable GetFragments(IApplicationContext applicationContext) where TFragment : IFragmentBase + { + return GetFragments(applicationContext, typeof(TFragment)); + } + + /// + /// Returns all fragment contexts that belong to a given fragment type. + /// + /// The application context. + /// The fragment type. + /// An enumeration of the filtered fragment contexts. + public IEnumerable GetFragments(IApplicationContext applicationContext, Type fragmentType) + { + return _dictionary.GetFragments(applicationContext, fragmentType); + } + + /// + /// Returns all fragment contexts that belong to a given application. + /// + /// The section where the fragment is embedded. + /// The scope where the fragment is embedded. + /// The application context. + /// An enumeration of the filtered fragment contexts. + public IEnumerable GetFragments(IApplicationContext applicationContext) + where TSection : ISection + where TScope : IScope + { + return GetFragments(applicationContext, typeof(TSection), typeof(TScope)); + } + + /// + /// Returns all fragment contexts that belong to a given application. + /// + /// The application context. + /// The section where the fragment is embedded. + /// The scope where the fragment is embedded. + /// An enumeration of the filtered fragment contexts. + public IEnumerable GetFragments(IApplicationContext applicationContext, Type section, Type scope) + { + return _dictionary.GetFragments(applicationContext, section, scope); + } + + /// + /// Returns all fragments that belong to a given application. + /// + /// The fragment type. + /// The section where the fragment is embedded. + /// The application context. + /// The scopes where the fragment is embedded. + /// An enumeration of the filtered fragments. + public IEnumerable GetFragments(IApplicationContext applicationContext, IEnumerable scopes) + where TFragment : IFragmentBase + where TSection : ISection + { + var effectiveScopes = (scopes?.Any() == true) ? scopes : [typeof(IScope)]; + + foreach (var item in _dictionary.GetFragmentItems(applicationContext, typeof(TFragment), typeof(TSection), effectiveScopes)) + { + yield return item.CreateInstance(); + } + } + + /// + /// Returns all fragment contexts that belong to a given application. + /// + /// The application context. + /// The section where the fragment is embedded. + /// The scopes where the fragment is embedded. + /// An enumeration of the filtered fragment contexts. + public IEnumerable GetFragments(IApplicationContext applicationContext, Type section, IEnumerable scopes) + { + var effectiveScopes = (scopes?.Any() == true) ? scopes : [typeof(IScope)]; + + foreach (var scope in effectiveScopes) + { + foreach (var item in GetFragments(applicationContext, section, effectiveScopes)) + { + yield return item; + } + } + } + + /// + /// Converts the fragments to HTML for a given section within the specified render context. + /// + /// The type of the render context. + /// The type of the visual tree. + /// The context in which rendering occurs. + /// The visual tree used for rendering. + /// The section where the fragment is embedded. + /// An enumeration of HTML nodes representing the rendered fragments. + public IEnumerable Render(TRenderContext renderContext, TVisualTree visualTree, Type section) + where TRenderContext : IRenderContext + where TVisualTree : IVisualTree + { + var applicationContext = renderContext?.PageContext?.ApplicationContext; + var scopes = renderContext?.PageContext?.Scopes ?? [typeof(IScope)]; + var items = _dictionary.GetFragmentItems(applicationContext, section, scopes); + + return items.Select(x => x.Render(renderContext, visualTree)); + } + + /// + /// Information about the component is collected and prepared for output in the log. + /// + private void Log() + { + if (!Fragments.Any()) + { + return; + } + + using var frame = new LogFrameSimple(_httpServerContext.Log); + var list = new List + { + I18N.Translate("webexpress.webcore:fragmentmanager.titel") + }; + + foreach (var fragment in Fragments.Distinct()) + { + list.Add + ( + string.Empty.PadRight(2) + + I18N.Translate("webexpress.webcore:fragmentmanager.fragment", fragment.FragmentId.ToString(), fragment.ApplicationContext?.ApplicationId) + ); + } + + _httpServerContext.Log.Info(string.Join(Environment.NewLine, list)); + } + + /// + /// Release of unmanaged resources reserved during use. + /// + public void Dispose() + { + _componentHub.PluginManager.AddPlugin -= OnAddPlugin; + _componentHub.PluginManager.RemovePlugin -= OnRemovePlugin; + _componentHub.ApplicationManager.AddApplication -= OnAddApplication; + _componentHub.ApplicationManager.RemoveApplication -= OnRemoveApplication; + + GC.SuppressFinalize(this); + } + } +} diff --git a/src/WebExpress.WebCore/WebFragment/IFragment.cs b/src/WebExpress.WebCore/WebFragment/IFragment.cs new file mode 100644 index 0000000..8bd90a4 --- /dev/null +++ b/src/WebExpress.WebCore/WebFragment/IFragment.cs @@ -0,0 +1,29 @@ +using WebExpress.WebCore.WebComponent; +using WebExpress.WebCore.WebHtml; +using WebExpress.WebCore.WebPage; + +namespace WebExpress.WebCore.WebFragment +{ + /// + /// Represents a fragment that is a part of a web component. + /// + public interface IFragment : IFragment + { + } + + /// + /// Represents a fragment that is a part of a web component. + /// + public interface IFragment : IComponent, IFragmentBase + where TRenderContext : IRenderContext + where TVisualTree : IVisualTree + { + /// + /// Converts the fragment to an HTML representation. + /// + /// The context in which the fragment is rendered. + /// The visual tree used for rendering the fragment. + /// An HTML node representing the rendered fragments. Can be null if no nodes are present. + IHtmlNode Render(TRenderContext renderContext, TVisualTree visualTree); + } +} diff --git a/src/WebExpress.WebCore/WebFragment/IFragmentBase.cs b/src/WebExpress.WebCore/WebFragment/IFragmentBase.cs new file mode 100644 index 0000000..99e4524 --- /dev/null +++ b/src/WebExpress.WebCore/WebFragment/IFragmentBase.cs @@ -0,0 +1,11 @@ +using WebExpress.WebCore.WebComponent; + +namespace WebExpress.WebCore.WebFragment +{ + /// + /// Defines the base interface for all fragments in the WebExpress framework. + /// + public interface IFragmentBase : IComponent + { + } +} diff --git a/src/WebExpress.WebCore/WebFragment/IFragmentContext.cs b/src/WebExpress.WebCore/WebFragment/IFragmentContext.cs new file mode 100644 index 0000000..fcbc476 --- /dev/null +++ b/src/WebExpress.WebCore/WebFragment/IFragmentContext.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using WebExpress.WebCore.WebApplication; +using WebExpress.WebCore.WebComponent; +using WebExpress.WebCore.WebCondition; +using WebExpress.WebCore.WebPlugin; + +namespace WebExpress.WebCore.WebFragment +{ + /// + /// Interface representing the context of a web fragment. + /// Provides access to plugin context, application context, conditions for activation, and caching behavior. + /// + public interface IFragmentContext : IContext + { + /// + /// Returns the context of the associated plugin. + /// + IPluginContext PluginContext { get; } + + /// + /// Returns the application context. + /// + IApplicationContext ApplicationContext { get; } + + /// + /// Gets the unique identifier for the fragment. + /// + IComponentId FragmentId { get; } + + /// + /// Returns the conditions that must be met for the component to be active. + /// + IEnumerable Conditions { get; } + + /// + /// Returns the section. + /// + Type Section { get; } + + /// + /// Returns the scope. + /// + Type Scope { get; } + + /// + /// Determines whether the component is created once and reused on each execution. + /// + bool Cache { get; } + } +} diff --git a/src/WebExpress.WebCore/WebFragment/IFragmentDynamic.cs b/src/WebExpress.WebCore/WebFragment/IFragmentDynamic.cs new file mode 100644 index 0000000..6a16ce4 --- /dev/null +++ b/src/WebExpress.WebCore/WebFragment/IFragmentDynamic.cs @@ -0,0 +1,32 @@ +using System.Collections.Generic; +using WebExpress.WebCore.WebComponent; +using WebExpress.WebCore.WebPage; + +namespace WebExpress.WebCore.WebFragment +{ + /// + /// Interface representing a dynamic fragment. + /// Provides methods for initialization and creation of fragments. + /// + public interface IFragmentDynamic + { + /// + /// Returns the context of the fragment. + /// + IFragmentContext Context { get; } + + /// + /// Initialization + /// + /// The context. + /// The page where the fragment is active. + void Initialization(IFragmentContext context, IPage page); + + /// + /// Creates fragments of a common type T. + /// + /// The created instances of the fragments. + IEnumerable Create() + where TComponent : IComponent; + } +} diff --git a/src/WebExpress.WebCore/WebFragment/IFragmentManager.cs b/src/WebExpress.WebCore/WebFragment/IFragmentManager.cs new file mode 100644 index 0000000..540524b --- /dev/null +++ b/src/WebExpress.WebCore/WebFragment/IFragmentManager.cs @@ -0,0 +1,117 @@ +using System; +using System.Collections.Generic; +using WebExpress.WebCore.WebApplication; +using WebExpress.WebCore.WebComponent; +using WebExpress.WebCore.WebHtml; +using WebExpress.WebCore.WebPage; +using WebExpress.WebCore.WebScope; +using WebExpress.WebCore.WebSection; + +namespace WebExpress.WebCore.WebFragment +{ + /// + /// Interface for managing web fragments. + /// + public interface IFragmentManager : IComponentManager + { + /// + /// An event that fires when a fragment is added. + /// + event EventHandler AddFragment; + + /// + /// An event that fires when a fragment is removed. + /// + event EventHandler RemoveFragment; + + /// + /// Returns the collection of fragment contexts. + /// + IEnumerable Fragments { get; } + + /// + /// Returns all fragment contexts that belong to a given fragment type. + /// + /// The fragment type. + /// An enumeration of the filtered fragment contexts. + IEnumerable GetFragments() where TFragment : IFragmentBase; + + /// + /// Returns all fragment contexts that belong to a given fragment type. + /// + /// The fragment type. + /// An enumeration of the filtered fragment contexts. + IEnumerable GetFragments(Type fragmentType); + + /// + /// Returns all fragment contexts that belong to a given fragment type. + /// + /// The fragment type.. + /// The application context. + /// An enumeration of the filtered fragment contexts. + IEnumerable GetFragments(IApplicationContext applicationContext) + where TFragment : IFragmentBase; + + /// + /// Returns all fragment contexts that belong to a given fragment type. + /// + /// The application context. + /// The fragment type. + /// An enumeration of the filtered fragment contexts. + IEnumerable GetFragments(IApplicationContext applicationContext, Type fragmentType); + + /// + /// Returns all fragment contexts that belong to a given application. + /// + /// The section where the fragment is embedded. + /// The scope where the fragment is embedded. + /// The application context. + /// An enumeration of the filtered fragment contexts. + IEnumerable GetFragments(IApplicationContext applicationContext) + where TSection : ISection + where TScope : IScope; + + /// + /// Returns all fragment contexts that belong to a given application. + /// + /// The application context. + /// The section where the fragment is embedded. + /// The scope where the fragment is embedded. + /// An enumeration of the filtered fragment contexts. + IEnumerable GetFragments(IApplicationContext applicationContext, Type section, Type scope); + + /// + /// Returns all fragments that belong to a given application. + /// + /// The fragment type. + /// The section where the fragment is embedded. + /// The application context. + /// The scopes where the fragment is embedded. + /// An enumeration of the filtered fragments. + public IEnumerable GetFragments(IApplicationContext applicationContext, IEnumerable scopes) + where TFragment : IFragmentBase + where TSection : ISection; + + /// + /// Returns all fragment contexts that belong to a given application. + /// + /// The application context. + /// The section where the fragment is embedded. + /// The scopes where the fragment is embedded. + /// An enumeration of the filtered fragment contexts. + IEnumerable GetFragments(IApplicationContext applicationContext, Type section, IEnumerable scopes); + + /// + /// Converts the fragments to HTML for a given section within the specified render context. + /// + /// The type of the render context. + /// The type of the visual tree. + /// The context in which rendering occurs. + /// The visual tree used for rendering. + /// The section where the fragment is embedded. + /// An enumeration of HTML nodes representing the rendered fragments. + IEnumerable Render(TRenderContext renderContext, TVisualTree visualTree, Type section) + where TRenderContext : IRenderContext + where TVisualTree : IVisualTree; + } +} diff --git a/src/WebExpress.WebCore/WebFragment/Model/FragmentDictionary.cs b/src/WebExpress.WebCore/WebFragment/Model/FragmentDictionary.cs new file mode 100644 index 0000000..1213440 --- /dev/null +++ b/src/WebExpress.WebCore/WebFragment/Model/FragmentDictionary.cs @@ -0,0 +1,259 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using WebExpress.WebCore.WebApplication; +using WebExpress.WebCore.WebPlugin; +using WebExpress.WebCore.WebScope; + +namespace WebExpress.WebCore.WebFragment.Model +{ + /// + /// Represents a dictionary that maps plugin contexts to application contexts, + /// which in turn maps to a dictionary of section types and inner maps of scope types and lists of FragmentItem objects. + /// Plugin -> Application -> Section -> Scope -> FragmentItem + /// + internal class FragmentDictionary + { + private readonly Dictionary>>>> _dict = []; + + /// + /// Returns all fragment contexts from the dictionary. + /// + public IEnumerable All => _dict.Values + .SelectMany(x => x.Values) + .SelectMany(x => x.Values) + .SelectMany(x => x.Values) + .SelectMany(x => x) + .Select(x => x.FragmentContext); + + /// + /// Adds a fragment item to the dictionary. + /// + /// The plugin context. + /// The application context. + /// The fragment item. + /// True if the fragment item was added successfully, false if an element with the same status code already exists. + public bool AddFragmentItem(IPluginContext pluginContext, IApplicationContext applicationContext, FragmentItem fragmentItem) + { + var type = fragmentItem.FragmentClass; + + if (type.GetInterface(typeof(IFragment<,>).Name) == null) + { + return false; + } + + if (!_dict.TryGetValue(pluginContext, out var applicationDict)) + { + applicationDict = []; + _dict[pluginContext] = applicationDict; + } + + if (!applicationDict.TryGetValue(applicationContext, out var sectionDict)) + { + sectionDict = []; + applicationDict[applicationContext] = sectionDict; + } + + if (!sectionDict.TryGetValue(fragmentItem.Section, out var scopeDict)) + { + scopeDict = []; + sectionDict[fragmentItem.Section] = scopeDict; + } + + if (!scopeDict.TryGetValue(fragmentItem.Scope, out var itemList)) + { + itemList = []; + scopeDict[fragmentItem.Scope] = itemList; + } + + if (!itemList.Any(x => x.FragmentClass == fragmentItem.FragmentClass)) + { + itemList.Add(fragmentItem); + + return true; + } + + return false; + } + + /// + /// Removes fragments from the dictionary. + /// + /// The plugin context. + /// An IEnumerable of fragment contexts that were removed. + public IEnumerable RemoveFragments(IPluginContext pluginContext) + { + var fragments = GetFragments(pluginContext); + + _dict.Remove(pluginContext); + + return fragments; + } + + /// + /// Removes fragments from the dictionary. + /// + /// The application context. + /// An IEnumerable of fragment contexts that were removed. + public IEnumerable RemoveFragments(IApplicationContext applicationContext) + { + foreach (var pluginKeyValue in _dict) + { + if (pluginKeyValue.Value.TryGetValue(applicationContext, out var sectionDict)) + { + pluginKeyValue.Value.Remove(applicationContext); + + if (pluginKeyValue.Value.Count == 0) + { + _dict.Remove(pluginKeyValue.Key); + } + + foreach (var item in sectionDict.Values + .SelectMany(x => x.Values) + .SelectMany(x => x) + .Select(x => x.FragmentContext)) + { + yield return item; + } + } + } + } + + /// + /// Returns all fragment contexts that belong to a given application. + /// + /// The application context. + /// The section where the fragment is embedded. + /// The scopes where the fragment is embedded. + /// An enumeration of the filtered fragment contexts. + public IEnumerable GetFragmentItems(IApplicationContext applicationContext, Type section, IEnumerable scopes) + { + return _dict.Values + .SelectMany(x => x) + .Where(x => x.Key == applicationContext) + .SelectMany(x => x.Value) + .Where(x => x.Key == section || section.IsAssignableFrom(x.Key)) + .SelectMany(x => x.Value) + .Where(x => scopes.Any(y => x.Key == y)) + .SelectMany(x => x.Value) + .OrderBy(x => x.Order); + } + + /// + /// Returns all fragment items that belong to a given application context, fragment type, section, and scopes. + /// + /// The application context. + /// The type of fragment. + /// The section where the fragment is embedded. + /// The scopes where the fragment is embedded. + /// An enumeration of the filtered fragment items. + public IEnumerable GetFragmentItems(IApplicationContext applicationContext, Type fragment, Type section, IEnumerable scopes) + { + return _dict.Values + .SelectMany(x => x) + .Where(x => x.Key == applicationContext) + .SelectMany(x => x.Value) + .Where(x => x.Key == section || section.IsAssignableFrom(x.Key)) + .SelectMany(x => x.Value) + .Where(x => scopes.Any(y => x.Key == y)) + .SelectMany(x => x.Value) + .Where(x => x.FragmentClass == fragment || fragment.IsAssignableFrom(x.FragmentClass)) + .OrderBy(x => x.Order); + } + + /// + /// Returns all fragment contexts for a given plugin context. + /// + /// The plugin context. + /// An IEnumerable of fragment contexts. + public IEnumerable GetFragments(IPluginContext pluginContext) + { + return _dict.Where(x => x.Key == pluginContext) + .SelectMany(x => x.Value.Values) + .SelectMany(x => x.Values) + .SelectMany(x => x.Values) + .SelectMany(x => x) + .Select(x => x.FragmentContext); + } + + /// + /// Returns all fragment contexts that belong to a given fragment type. + /// + /// The fragment type. + /// An enumeration of the filtered fragment contexts. + public IEnumerable GetFragments(Type fragmentType) + { + return _dict.Values + .SelectMany(x => x) + .SelectMany(x => x.Value) + .SelectMany(x => x.Value) + .SelectMany(x => x.Value) + .Where(x => x.FragmentClass == fragmentType) + .OrderBy(x => x.Order) + .Select(x => x.FragmentContext); + } + + /// + /// Returns all fragment contexts that belong to a given fragment type. + /// + /// The application context. + /// The fragment type. + /// An enumeration of the filtered fragment contexts. + public IEnumerable GetFragments(IApplicationContext applicationContext, Type fragmentType) + { + return _dict.Values + .SelectMany(x => x) + .Where(x => x.Key == applicationContext) + .SelectMany(x => x.Value) + .SelectMany(x => x.Value) + .SelectMany(x => x.Value) + .Where(x => x.FragmentClass == fragmentType) + .OrderBy(x => x.Order) + .Select(x => x.FragmentContext); + } + + /// + /// Returns all fragment contexts that belong to a given application. + /// + /// The application context. + /// The section where the fragment is embedded. + /// The scope where the fragment is embedded. + /// An enumeration of the filtered fragment contexts. + public IEnumerable GetFragments(IApplicationContext applicationContext, Type section, Type scope) + { + scope ??= typeof(IScope); + + return _dict.Values + .SelectMany(x => x) + .Where(x => x.Key == applicationContext) + .SelectMany(x => x.Value) + .Where(x => x.Key == section || section.IsAssignableFrom(x.Key)) + .SelectMany(x => x.Value) + .Where(x => x.Key == scope) + .SelectMany(x => x.Value) + .OrderBy(x => x.Order) + .Select(x => x.FragmentContext); + } + + /// + /// Checks if the dictionary contains the specified plugin context. + /// + /// The plugin context to check for. + /// True if the plugin context exists in the dictionary, otherwise false. + public bool Contains(IPluginContext pluginContext) + { + return _dict.ContainsKey(pluginContext); + } + + /// + /// Checks if the dictionary contains the specified plugin context and application context. + /// + /// The plugin context to check for. + /// The application context to check for. + /// True if the plugin context and application context exist in the dictionary, otherwise false. + public bool Contains(IPluginContext pluginContext, IApplicationContext applicationContext) + { + return _dict.TryGetValue(pluginContext, out var appDict) && appDict.ContainsKey(applicationContext); + } + } +} diff --git a/src/WebExpress.WebCore/WebFragment/Model/FragmentItem.cs b/src/WebExpress.WebCore/WebFragment/Model/FragmentItem.cs new file mode 100644 index 0000000..4cc5cfb --- /dev/null +++ b/src/WebExpress.WebCore/WebFragment/Model/FragmentItem.cs @@ -0,0 +1,170 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using WebExpress.WebCore.WebApplication; +using WebExpress.WebCore.WebComponent; +using WebExpress.WebCore.WebCondition; +using WebExpress.WebCore.WebHtml; +using WebExpress.WebCore.WebMessage; +using WebExpress.WebCore.WebPage; +using WebExpress.WebCore.WebPlugin; + +namespace WebExpress.WebCore.WebFragment.Model +{ + /// + /// Fragments are components that can be integrated into pages to dynamically expand functionalities. + /// + internal class FragmentItem : IDisposable + { + private IFragmentBase _instance; + private readonly IComponentHub _componentHub; + private readonly IHttpServerContext _httpServerContext; + private static readonly Dictionary _delegateCache = []; + + /// + /// Returns the context of the associated plugin. + /// + public IPluginContext PluginContext { get; set; } + + /// + /// Returns the application context. + /// + public IApplicationContext ApplicationContext { get; set; } + + /// + /// Returns the fragment context. + /// + public IFragmentContext FragmentContext { get; set; } + + /// + /// The type of fragment. + /// + public Type FragmentClass { get; set; } + + /// + /// Returns the section. + /// + public Type Section { get; set; } + + /// + /// Returns the scope. + /// + public Type Scope { get; set; } + + /// + /// Returns the conditions that must be met for the component to be active. + /// + public ICollection Conditions { get; set; } + + /// + /// The order of the fragment. + /// + public int Order { get; set; } + + /// + /// Determines whether the component is created once and reused on each execution. + /// + public bool Cache { get; set; } + + /// + /// Initializes a new instance of the class. + /// + /// The component hub. + /// The context of the HTTP server. + public FragmentItem(IComponentHub componentHub, IHttpServerContext httpServerContext) + { + _componentHub = componentHub; + _httpServerContext = httpServerContext; + } + + /// + /// Create the instance of the component. + /// + public TFragment CreateInstance() where TFragment : IFragmentBase + { + var instance = _instance; + + instance ??= ComponentActivator.CreateInstance(FragmentClass, FragmentContext, _httpServerContext, _componentHub, FragmentContext); + + if (Cache) + { + _instance = instance; + } + + return (TFragment)instance; + } + + /// + /// Processes the fragments for a given section within the specified render context. + /// + /// The type of the render context. + /// The type of the visual tree. + /// The context in which rendering occurs. + /// The visual tree to be rendered. + /// An HTML node representing the rendered fragments. Can be null if no nodes are present. + public IHtmlNode Render(TRenderContext renderContext, TVisualTree visualTree) + where TRenderContext : IRenderContext + where TVisualTree : IVisualTree + { + var instance = CreateInstance(); + + if (CheckConditions(renderContext?.Request)) + { + if (!_delegateCache.TryGetValue(FragmentClass, out var del)) + { + // create and compile the expression + var renderContextType = FragmentClass.GetInterface(typeof(IFragment<,>).Name).GetGenericArguments()[0]; + var visualTreeType = FragmentClass.GetInterface(typeof(IFragment<,>).Name).GetGenericArguments()[1]; + var renderContextParam = Expression.Parameter(renderContextType, "renderContext"); + var visualTreeParam = Expression.Parameter(visualTreeType, "visualTree"); + var renderMethod = FragmentClass.GetMethod("Render", [renderContextType, visualTreeType]); + var callProzessMethod = Expression.Call + ( + Expression.Constant(instance), + renderMethod, + renderContextParam, + visualTreeParam + ); + var lambda = Expression.Lambda(callProzessMethod, renderContextParam, visualTreeParam) + .Compile(); + + _delegateCache[FragmentClass] = lambda; + del = lambda; + } + + // execute the cached delegate + var html = del.DynamicInvoke(renderContext, visualTree) as IHtmlNode; + + return html; + } + + return null; + } + + /// + /// Checks the component to see if they are displayed or disabled. + /// + /// The request. + /// True if the fragment is active, false otherwise. + public bool CheckConditions(Request request) + { + return !FragmentContext.Conditions.Any() || FragmentContext.Conditions.All(x => x.Fulfillment(request)); + } + /// + /// Performs application-specific tasks related to sharing, returning, or resetting unmanaged resources. + /// + public void Dispose() + { + } + + /// + /// Convert the resource element to a string. + /// + /// The resource element in its string representation. + public override string ToString() + { + return $"Fragment: '{FragmentContext.FragmentId}'"; + } + } +} diff --git a/src/WebExpress.WebCore/WebHtml/Css.cs b/src/WebExpress.WebCore/WebHtml/Css.cs index c69ef69..0b385f1 100644 --- a/src/WebExpress.WebCore/WebHtml/Css.cs +++ b/src/WebExpress.WebCore/WebHtml/Css.cs @@ -2,6 +2,9 @@ namespace WebExpress.WebCore.WebHtml { + /// + /// Provides utility methods for working with CSS classes. + /// public static class Css { /// diff --git a/src/WebExpress.WebCore/WebHtml/Favicon.cs b/src/WebExpress.WebCore/WebHtml/Favicon.cs index 1a84c9d..1498868 100644 --- a/src/WebExpress.WebCore/WebHtml/Favicon.cs +++ b/src/WebExpress.WebCore/WebHtml/Favicon.cs @@ -1,5 +1,8 @@ namespace WebExpress.WebCore.WebHtml { + /// + /// Represents a favicon with a URL and media type. + /// public class Favicon { /// @@ -13,7 +16,7 @@ public class Favicon public TypeFavicon Mediatype { get; set; } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The uri. /// The media type. @@ -24,17 +27,16 @@ public Favicon(string url, TypeFavicon mediatype) } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The uri. - /// The media type. public Favicon(string url) { Url = url; } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The uri. /// The media type. diff --git a/src/WebExpress.WebCore/WebHtml/HtmlAttribute.cs b/src/WebExpress.WebCore/WebHtml/HtmlAttribute.cs index 4f53216..21057d1 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlAttribute.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlAttribute.cs @@ -2,6 +2,9 @@ namespace WebExpress.WebCore.WebHtml { + /// + /// Represents an HTML attribute that can be added to an HTML element. + /// public class HtmlAttribute : IHtmlAttribute { /// @@ -15,7 +18,7 @@ public class HtmlAttribute : IHtmlAttribute public string Value { get; set; } /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlAttribute() { @@ -23,7 +26,7 @@ public HtmlAttribute() } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The name. public HtmlAttribute(string name) @@ -32,7 +35,7 @@ public HtmlAttribute(string name) } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The name. /// The value. @@ -52,7 +55,7 @@ public virtual void ToString(StringBuilder builder, int deep) builder.Append(Name); builder.Append("=\""); builder.Append(Value); - builder.Append("\""); + builder.Append('"'); } } } diff --git a/src/WebExpress.WebCore/WebHtml/HtmlAttributeNoneValue.cs b/src/WebExpress.WebCore/WebHtml/HtmlAttributeNoneValue.cs index 54ce20e..a586231 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlAttributeNoneValue.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlAttributeNoneValue.cs @@ -4,7 +4,7 @@ namespace WebExpress.WebCore.WebHtml { /// /// An attribute without value. - /// e.g. required in input + /// e.g. required in input required /// public class HtmlAttributeNoneValue : IHtmlAttribute { @@ -14,7 +14,7 @@ public class HtmlAttributeNoneValue : IHtmlAttribute public string Name { get; set; } /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlAttributeNoneValue() { @@ -22,7 +22,7 @@ public HtmlAttributeNoneValue() } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The name. public HtmlAttributeNoneValue(string name) diff --git a/src/WebExpress.WebCore/WebHtml/HtmlComment.cs b/src/WebExpress.WebCore/WebHtml/HtmlComment.cs index 36bdd09..66a63f7 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlComment.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlComment.cs @@ -2,6 +2,9 @@ namespace WebExpress.WebCore.WebHtml { + /// + /// Represents an HTML comment node. + /// public class HtmlComment : IHtmlNode { /// @@ -10,15 +13,14 @@ public class HtmlComment : IHtmlNode public string Text { get; set; } /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlComment() { - } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The text. public HtmlComment(string text) diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElement.cs b/src/WebExpress.WebCore/WebHtml/HtmlElement.cs index 914718c..141c3de 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElement.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElement.cs @@ -1,401 +1,463 @@ -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace WebExpress.WebCore.WebHtml -{ - /// - /// The basis of all html elements (see RfC 1866). - /// - public class HtmlElement : IHtmlNode - { - /// - /// Returns or sets the name. des Attributes - /// - protected string ElementName { get; set; } - - /// - /// Returns or sets the attributes. - /// - protected List Attributes { get; } = new List(); - - /// - /// Returns or sets the elements. - /// - protected List Elements { get; } = new List(); - - /// - /// Returns or sets the id. - /// - public string Id - { - get => GetAttribute("id"); - set => SetAttribute("id", value); - } - - /// - /// Returns or sets the css class. - /// - public string Class - { - get => GetAttribute("class"); - set => SetAttribute("class", value); - } - - /// - /// Returns or sets the css style. - /// - public string Style - { - get => GetAttribute("style"); - set => SetAttribute("style", value); - } - - /// - /// Returns or sets the role. - /// - public string Role - { - get => GetAttribute("role"); - set => SetAttribute("role", value); - } - - /// - /// Returns or sets the html5 data attribute. - /// - public string DataToggle - { - get => GetAttribute("data-toggle"); - set => SetAttribute("data-toggle", value); - } - - /// - /// Returns or sets the html5 data attribute. - /// - public string DataProvide - { - get => GetAttribute("data-provide"); - set => SetAttribute("data-provide", value); - } - - /// - /// Returns or sets the on click attribute. - /// - public string OnClick - { - get => GetAttribute("onclick"); - set => SetAttribute("onclick", value); - } - - /// - /// Determines whether the element is inline. - /// - public bool Inline { get; set; } - - /// - /// Determines whether the element needs an end tag. - /// e.g.: true =
false =
- ///
- public bool CloseTag { get; protected set; } - - /// - /// Constructor - /// - /// The name of the item. - public HtmlElement(string name, bool closeTag = true) - { - ElementName = name; - CloseTag = closeTag; - } - - /// - /// Constructor - /// - /// The name of the item. - public HtmlElement(string name, bool closeTag, params IHtml[] nodes) - : this(name, closeTag) - { - foreach (var v in nodes) - { - if (v is HtmlAttribute attr) - { - Attributes.Add(attr); - } - else if (v is HtmlElement element) - { - Elements.Add(element); - } - else if (v is HtmlText text) - { - Elements.Add(text); - } - } - } - - /// - /// Returns the value of an attribute. - /// - /// The attribute name. - /// The value of the attribute. - protected string GetAttribute(string name) - { - var a = Attributes.Where(x => x.Name == name).FirstOrDefault(); - - if (a != null) - { - return a is HtmlAttribute ? (a as HtmlAttribute).Value : string.Empty; - } - - return string.Empty; - } - - /// - /// Checks whether an attribute is set. - /// - /// The attribute name. - /// True if attribute exists, false otherwise. - protected bool HasAttribute(string name) - { - var a = Attributes.Where(x => x.Name == name).FirstOrDefault(); - - return (a != null); - } - - /// - /// Sets the value of an attribute. - /// - /// The attribute name. - /// The value of the attribute. - protected void SetAttribute(string name, string value) - { - var a = Attributes.Where(x => x.Name == name).FirstOrDefault(); - - if (a != null) - { - if (string.IsNullOrWhiteSpace(value)) - { - Attributes.Remove(a); - } - else if (a is HtmlAttribute) - { - (a as HtmlAttribute).Value = value; - } - } - else - { - if (!string.IsNullOrWhiteSpace(value)) - { - Attributes.Add(new HtmlAttribute(name, value)); - } - } - } - - /// - /// Setzt den Wert eines Attributs - /// - /// The attribute name. - protected void SetAttribute(string name) - { - var a = Attributes.Where(x => x.Name == name).FirstOrDefault(); - - if (a == null) - { - Attributes.Add(new HtmlAttributeNoneValue(name)); - } - } - - /// - /// Removes an attribute. - /// - /// The attribute name. - protected void RemoveAttribute(string name) - { - var a = Attributes.Where(x => x.Name == name).FirstOrDefault(); - - if (a != null) - { - Attributes.Remove(a); - } - } - - /// - /// Returns an element based on its name. - /// - /// The element name. - /// The element. - protected HtmlElement GetElement(string name) - { - var a = Elements.Where(x => x is HtmlElement && (x as HtmlElement).ElementName == name).FirstOrDefault(); - - return a as HtmlElement; - } - - /// - /// Sets an element. - /// - /// The element. - protected void SetElement(HtmlElement element) - { - if (element != null) - { - var a = Elements.Where(x => x is HtmlElement && (x as HtmlElement).ElementName == element.ElementName); - - foreach (var v in a) - { - Elements.Remove(v); - } - - Elements.Add(element); - } - } - - /// - /// Returns the text. - /// - /// The text. - protected string GetText() - { - var a = Elements.Where(x => x is HtmlText).Select(x => (x as HtmlText).Value); - - return string.Join(" ", a); - } - - /// - /// Convert to a string using a StringBuilder. - /// - /// The string builder. - /// The call depth. - public virtual void ToString(StringBuilder builder, int deep) - { - ToPreString(builder, deep); - - var closeTag = false; - var nl = true; - - if (Elements.Count == 1 && Elements.First() is HtmlText) - { - closeTag = true; - nl = false; - - Elements.First().ToString(builder, 0); - } - else if (Elements.Count > 0) - { - closeTag = true; - var count = builder.Length; - - foreach (var v in Elements.Where(x => x != null)) - { - v.ToString(builder, deep + 1); - } - - if (count == builder.Length) - { - nl = false; - } - } - else if (Elements.Count == 0) - { - nl = false; - } - - if (closeTag || CloseTag) - { - ToPostString(builder, deep, nl); - } - } - - /// - /// Convert to a string using a StringBuilder. - /// - /// The string builder. - /// The call depth. - protected virtual void ToPreString(StringBuilder builder, int deep) - { - if (!Inline) - { - builder.AppendLine(); - builder.Append(string.Empty.PadRight(deep)); - } - - builder.Append("<"); - builder.Append(ElementName); - foreach (var v in Attributes) - { - builder.Append(" "); - v.ToString(builder, 0); - } - builder.Append(">"); - } - - /// - /// Convert to a string using a string builder. - /// - /// The string builder. - /// The call depth. - /// Start the closing tag on a new line. - protected virtual void ToPostString(StringBuilder builder, int deep, bool nl = true) - { - if (!Inline && nl) - { - builder.AppendLine(); - builder.Append(string.Empty.PadRight(deep)); - } - - builder.Append(""); - } - - /// - /// Sets the value of an user-defined attribute. - /// - /// The attribute name. - /// The value of the attribute. - public void AddUserAttribute(string name, string value) - { - SetAttribute(name, value); - } - - /// - /// Returns the value of an user-defined attribute. - /// - /// The attribute name. - /// The value of the attribute. - public string GetUserAttribute(string name) - { - return GetAttribute(name); - } - - /// - /// Checks if a user-defined attribute is set. - /// - /// The attribute name. - /// True wenn Attribut vorhanden, false sonst - public bool HasUserAttribute(string name) - { - return HasAttribute(name); - } - - /// - /// Removes an user-defined attribute. - /// - /// The attribute name. - protected void RemoveUserAttribute(string name) - { - RemoveAttribute(name); - } - - /// - /// Convert to String. - /// - /// The object as a string. - public override string ToString() - { - var builder = new StringBuilder(); - ToString(builder, 0); - - return builder.ToString(); - } - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace WebExpress.WebCore.WebHtml +{ + /// + /// The basis of all html elements (see RfC 1866). + /// + public class HtmlElement : IHtmlNode + { + private readonly List _elements = []; + private readonly List _attributes = []; + + /// + /// Returns or sets the name. des Attributes + /// + protected string ElementName { get; set; } + + /// + /// Returns or sets the attributes. + /// + protected IEnumerable Attributes => _attributes; + + /// + /// Returns the elements. + /// + protected IEnumerable Elements => _elements; + + /// + /// Returns or sets the id. + /// + public string Id + { + get => GetAttribute("id"); + set => SetAttribute("id", value); + } + + /// + /// Returns or sets the css class. + /// + public string Class + { + get => GetAttribute("class"); + set => SetAttribute("class", value); + } + + /// + /// Returns or sets the css style. + /// + public string Style + { + get => GetAttribute("style"); + set => SetAttribute("style", value); + } + + /// + /// Returns or sets the role. + /// + public string Role + { + get => GetAttribute("role"); + set => SetAttribute("role", value); + } + + /// + /// Returns or sets the html5 data attribute. + /// + public string DataToggle + { + get => GetAttribute("data-toggle"); + set => SetAttribute("data-toggle", value); + } + + /// + /// Returns or sets the html5 data attribute. + /// + public string DataProvide + { + get => GetAttribute("data-provide"); + set => SetAttribute("data-provide", value); + } + + /// + /// Returns or sets the on click attribute. + /// + public string OnClick + { + get => GetAttribute("onclick"); + set => SetAttribute("onclick", value); + } + + /// + /// Determines whether the element is inline. + /// + public bool Inline { get; set; } + + /// + /// Determines whether the element needs an end tag. + /// e.g.: true =
false =
+ ///
+ public bool CloseTag { get; protected set; } + + /// + /// Initializes a new instance of the class. + /// + /// The name of the HTML element. + /// A boolean value indicating whether the element requires a closing tag. Default is true. + public HtmlElement(string name, bool closeTag = true) + { + + ElementName = name; + + CloseTag = closeTag; + + } + + /// + /// Initializes a new instance of the class. + /// + /// The name of the HTML element. + /// A boolean value indicating whether the element requires a closing tag. + /// An array of IHtml nodes to be added to the element. + public HtmlElement(string name, bool closeTag, params IHtml[] nodes) + : this(name, closeTag) + { + foreach (var v in nodes) + { + if (v is HtmlAttribute attr) + { + _attributes.Add(attr); + } + else if (v is HtmlElement element) + { + _elements.Add(element); + } + else if (v is HtmlText text) + { + _elements.Add(text); + } + } + } + + /// + /// Adds one or more elements to the html element. + /// + /// The elements to add. + public void Add(params IHtmlNode[] elements) + { + _elements.AddRange(elements); + } + + /// + /// Adds one or more elements to the html element. + /// + /// The elements to add. + public void Add(IEnumerable elements) + { + _elements.AddRange(elements); + } + + /// + /// Adds one or more elements to the beginning of the html element. + /// + /// The elements to add. + public void AddFirst(params IHtmlNode[] elements) + { + _elements.InsertRange(0, elements); + } + /// + /// Adds one or more attributes to the html element. + /// + /// The attributes to add. + public void Add(params IHtmlAttribute[] attributes) + { + _attributes.AddRange(attributes); + } + + /// + /// Clear all elements frrom the html element. + /// + public void Clear() + { + _elements.Clear(); + } + + /// + /// Clear all elements from the html element that match the given predicate. + /// + /// The predicate to match elements. + protected void Clear(Func predicate) + { + _elements.RemoveAll(new Predicate(predicate)); + } + /// + /// Returns the value of an attribute. + /// + /// The attribute name. + /// The value of the attribute. + protected string GetAttribute(string name) + { + var a = _attributes.Where(x => x.Name == name).FirstOrDefault(); + + if (a != null) + { + return a is HtmlAttribute ? (a as HtmlAttribute).Value : string.Empty; + } + + return string.Empty; + } + + /// + /// Checks whether an attribute is set. + /// + /// The attribute name. + /// True if attribute exists, false otherwise. + protected bool HasAttribute(string name) + { + var a = _attributes.Where(x => x.Name == name).FirstOrDefault(); + + return (a != null); + } + + /// + /// Sets the value of an attribute. + /// + /// The attribute name. + /// The value of the attribute. + protected void SetAttribute(string name, string value) + { + var a = _attributes.Where(x => x.Name == name).FirstOrDefault(); + + if (a != null) + { + if (string.IsNullOrWhiteSpace(value)) + { + _attributes.Remove(a); + } + else if (a is HtmlAttribute) + { + (a as HtmlAttribute).Value = value; + } + } + else + { + if (!string.IsNullOrWhiteSpace(value)) + { + _attributes.Add(new HtmlAttribute(name, value)); + } + } + } + + /// + /// Setzt den Wert eines Attributs + /// + /// The attribute name. + protected void SetAttribute(string name) + { + var a = _attributes.Where(x => x.Name == name).FirstOrDefault(); + + if (a == null) + { + _attributes.Add(new HtmlAttributeNoneValue(name)); + } + } + + /// + /// Removes an attribute. + /// + /// The attribute name. + protected void RemoveAttribute(string name) + { + var a = _attributes.Where(x => x.Name == name).FirstOrDefault(); + + if (a != null) + { + _attributes.Remove(a); + } + } + + /// + /// Returns an element based on its name. + /// + /// The element name. + /// The element. + protected HtmlElement GetElement(string name) + { + var a = _elements.Where(x => x is HtmlElement && (x as HtmlElement).ElementName == name).FirstOrDefault(); + + return a as HtmlElement; + } + + /// + /// Sets an element. + /// + /// The element. + protected void SetElement(HtmlElement element) + { + if (element != null) + { + var a = _elements.Where(x => x is HtmlElement && (x as HtmlElement).ElementName == element.ElementName); + + foreach (var v in a) + { + _elements.Remove(v); + } + + _elements.Add(element); + } + } + + /// + /// Returns the text. + /// + /// The text. + protected string GetText() + { + var a = _elements.Where(x => x is HtmlText).Select(x => (x as HtmlText).Value); + + return string.Join(" ", a); + } + + /// + /// Convert to a string using a StringBuilder. + /// + /// The string builder. + /// The call depth. + public virtual void ToString(StringBuilder builder, int deep) + { + var closeTag = false; + var nl = true; + + ToPreString(builder, deep); + + if (_elements.Count == 1 && Elements.First() is HtmlText) + { + closeTag = true; + nl = false; + + _elements.First().ToString(builder, 0); + } + else if (_elements.Count > 0) + { + closeTag = true; + var count = builder.Length; + + foreach (var v in Elements.Where(x => x != null)) + { + v.ToString(builder, deep + 1); + } + + if (count == builder.Length) + { + nl = false; + } + } + else if (_elements.Count == 0) + { + nl = false; + } + + if (closeTag || CloseTag) + { + ToPostString(builder, deep, nl); + } + } + + /// + /// Converts the element to a string and appends it to the provided StringBuilder. + /// + /// The StringBuilder to append the string representation to. + /// The depth of the element in the HTML hierarchy, used for indentation. + protected virtual void ToPreString(StringBuilder builder, int deep) + { + if (!Inline) + { + builder.AppendLine(); + builder.Append(string.Empty.PadRight(deep)); + } + + builder.Append('<'); + builder.Append(ElementName); + foreach (var attribute in Attributes) + { + builder.Append(' '); + attribute.ToString(builder, 0); + } + + builder.Append('>'); + } + + /// + /// Converts the element to a string and appends the closing tag to the provided StringBuilder. + /// + /// The StringBuilder to append the string representation to. + /// The depth of the element in the HTML hierarchy, used for indentation. + /// Indicates whether the closing tag should start on a new line. + protected virtual void ToPostString(StringBuilder builder, int deep, bool nl = true) + { + if (!Inline && nl) + { + builder.AppendLine(); + builder.Append(string.Empty.PadRight(deep)); + } + + builder.Append("'); + } + + /// + /// Sets the value of an user-defined attribute. + /// + /// The attribute name. + /// The value of the attribute. + public void AddUserAttribute(string name, string value) + { + SetAttribute(name, value); + } + + /// + /// Returns the value of an user-defined attribute. + /// + /// The attribute name. + /// The value of the attribute. + public string GetUserAttribute(string name) + { + return GetAttribute(name); + } + + /// + /// Checks if a user-defined attribute is set. + /// + /// The attribute name. + /// True wenn Attribut vorhanden, false sonst + public bool HasUserAttribute(string name) + { + return HasAttribute(name); + } + + /// + /// Removes an user-defined attribute. + /// + /// The attribute name. + protected void RemoveUserAttribute(string name) + { + RemoveAttribute(name); + } + + /// + /// Convert to String. + /// + /// The object as a string. + public override string ToString() + { + var builder = new StringBuilder(); + ToString(builder, 0); + + return builder.ToString(); + } + } +} diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementEditDel.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementEditDel.cs index da10b25..dd94171 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementEditDel.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementEditDel.cs @@ -14,7 +14,7 @@ public class HtmlElementEditDel : HtmlElement, IHtmlElementEdit public string Text { get => string.Join(string.Empty, Elements.Where(x => x is HtmlText).Select(x => (x as HtmlText).Value)); - set { Elements.Clear(); Elements.Add(new HtmlText(value)); } + set { Clear(); Add(new HtmlText(value)); } } /// @@ -37,7 +37,7 @@ public string DateTime } /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementEditDel() : base("del") @@ -46,7 +46,7 @@ public HtmlElementEditDel() } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementEditDel(string text) @@ -56,13 +56,13 @@ public HtmlElementEditDel(string text) } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementEditDel(params IHtmlNode[] nodes) : this() { - Elements.AddRange(nodes); + Add(nodes); } /// diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementEditIns.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementEditIns.cs index cc832e0..94f9f79 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementEditIns.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementEditIns.cs @@ -14,7 +14,7 @@ public class HtmlElementEditIns : HtmlElement, IHtmlElementEdit public string Text { get => string.Join("", Elements.Where(x => x is HtmlText).Select(x => (x as HtmlText).Value)); - set { Elements.Clear(); Elements.Add(new HtmlText(value)); } + set { Clear(); Add(new HtmlText(value)); } } /// @@ -37,7 +37,7 @@ public string DateTime } /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementEditIns() : base("ins") @@ -46,7 +46,7 @@ public HtmlElementEditIns() } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementEditIns(string text) @@ -56,13 +56,13 @@ public HtmlElementEditIns(string text) } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementEditIns(params IHtmlNode[] nodes) : this() { - Elements.AddRange(nodes); + Add(nodes); } /// diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementEmbeddedEmbed.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementEmbeddedEmbed.cs index 09ad54e..56b5713 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementEmbeddedEmbed.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementEmbeddedEmbed.cs @@ -12,10 +12,10 @@ public class HtmlElementEmbeddedEmbed : HtmlElement, IHtmlElementEmbedded /// /// Returns the elements. /// - public new List Elements => base.Elements; + public new IEnumerable Elements => base.Elements; /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementEmbeddedEmbed() : base("embed") @@ -24,23 +24,13 @@ public HtmlElementEmbeddedEmbed() } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementEmbeddedEmbed(params IHtmlNode[] nodes) : this() { - Elements.AddRange(nodes); - } - - /// - /// Constructor - /// - /// The content of the html element. - public HtmlElementEmbeddedEmbed(IEnumerable nodes) - : this() - { - base.Elements.AddRange(nodes); + Add(nodes); } } } diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementEmbeddedIframe.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementEmbeddedIframe.cs index fdcef6a..207a20b 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementEmbeddedIframe.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementEmbeddedIframe.cs @@ -11,10 +11,10 @@ public class HtmlElementEmbeddedIframe : HtmlElement, IHtmlElementEmbedded /// /// Returns the elements. /// - public new List Elements => base.Elements; + public new IEnumerable Elements => base.Elements; /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementEmbeddedIframe() : base("iframe") @@ -23,23 +23,13 @@ public HtmlElementEmbeddedIframe() } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementEmbeddedIframe(params IHtmlNode[] nodes) : this() { - Elements.AddRange(nodes); - } - - /// - /// Constructor - /// - /// The content of the html element. - public HtmlElementEmbeddedIframe(IEnumerable nodes) - : this() - { - base.Elements.AddRange(nodes); + Add(nodes); } } } diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementEmbeddedObject.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementEmbeddedObject.cs index 2729205..17ed0c9 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementEmbeddedObject.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementEmbeddedObject.cs @@ -13,10 +13,10 @@ public class HtmlElementEmbeddedObject : HtmlElement, IHtmlElementEmbedded /// /// Returns the elements. /// - public new List Elements => base.Elements; + public new IEnumerable Elements => base.Elements; /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementEmbeddedObject() : base("object") @@ -24,23 +24,13 @@ public HtmlElementEmbeddedObject() } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementEmbeddedObject(params IHtmlNode[] nodes) : this() { - Elements.AddRange(nodes); - } - - /// - /// Constructor - /// - /// The content of the html element. - public HtmlElementEmbeddedObject(IEnumerable nodes) - : this() - { - base.Elements.AddRange(nodes); + Add(nodes); } } } diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementEmbeddedParam.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementEmbeddedParam.cs index 603f667..32f066e 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementEmbeddedParam.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementEmbeddedParam.cs @@ -2,17 +2,16 @@ { /// /// Represents a parameter for a plugin that can be used for the display - /// of an embedded element. + /// of an object embedded element. /// public class HtmlElementEmbeddedParam : HtmlElement, IHtmlElementEmbedded { /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementEmbeddedParam() : base("param", false) { - } } } diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementEmbeddedPicture.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementEmbeddedPicture.cs index 7af7573..d4f9bf8 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementEmbeddedPicture.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementEmbeddedPicture.cs @@ -10,10 +10,10 @@ public class HtmlElementEmbeddedPicture : HtmlElement, IHtmlElementEmbedded /// /// Returns the elements. /// - public new List Elements => base.Elements; + public new IEnumerable Elements => base.Elements; /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementEmbeddedPicture() : base("picture") @@ -21,23 +21,13 @@ public HtmlElementEmbeddedPicture() } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementEmbeddedPicture(params IHtmlNode[] nodes) : this() { - Elements.AddRange(nodes); - } - - /// - /// Constructor - /// - /// The content of the html element. - public HtmlElementEmbeddedPicture(IEnumerable nodes) - : this() - { - base.Elements.AddRange(nodes); + Add(nodes); } } } diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementEmbeddedSource.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementEmbeddedSource.cs index 1e9faab..7460214 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementEmbeddedSource.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementEmbeddedSource.cs @@ -2,12 +2,12 @@ { /// /// Allows authors to specify alternative media resources (e.g., different audio or video - /// formats) for media elements such as public class HtmlElementEmbeddedSource : HtmlElement, IHtmlElementEmbedded { /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementEmbeddedSource() : base("source", false) diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementFieldButton.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementFieldButton.cs index 9605713..0ea7131 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementFieldButton.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementFieldButton.cs @@ -7,7 +7,7 @@ namespace WebExpress.WebCore.WebHtml /// /// Represents a button element. /// - public class HtmlElementFieldButton : HtmlElement, IHtmlFormularItem + public class HtmlElementFieldButton : HtmlElement, IHtmlElementFormItem { /// /// Returns or sets the name of the input field. @@ -24,13 +24,13 @@ public string Name public string Text { get => string.Join("", Elements.Where(x => x is HtmlText).Select(x => (x as HtmlText).Value)); - set { Elements.Clear(); Elements.Add(new HtmlText(value)); } + set { Clear(); Add(new HtmlText(value)); } } /// /// Returns the elements. /// - public new List Elements => base.Elements; + public new IEnumerable Elements => base.Elements; /// /// Returns or sets a value. @@ -78,7 +78,7 @@ public string Title } /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementFieldButton() : base("button") @@ -87,7 +87,7 @@ public HtmlElementFieldButton() } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementFieldButton(string text) @@ -97,23 +97,13 @@ public HtmlElementFieldButton(string text) } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementFieldButton(params IHtmlNode[] nodes) : this() { - Elements.AddRange(nodes); - } - - /// - /// Constructor - /// - /// The content of the html element. - public HtmlElementFieldButton(IEnumerable nodes) - : this() - { - Elements.AddRange(nodes); + Add(nodes); } /// @@ -123,11 +113,6 @@ public HtmlElementFieldButton(IEnumerable nodes) /// The call depth. public override void ToString(StringBuilder builder, int deep) { - //if (Type == "submit" || Type == "reset") - //{ - // ElementName = "input"; - //} - base.ToString(builder, deep); } } diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementFieldInput.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementFieldInput.cs index 12d7f9e..af9072f 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementFieldInput.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementFieldInput.cs @@ -6,7 +6,7 @@ namespace WebExpress.WebCore.WebHtml /// Represents a field for user input of a specific type. The type (radio button, checkbox, /// text input, etc.) is specified using the type attribute. /// - public class HtmlElementFieldInput : HtmlElement, IHtmlFormularItem + public class HtmlElementFieldInput : HtmlElement, IHtmlElementFormItem { /// /// Returns or sets the name of the input field. @@ -181,7 +181,7 @@ public string Form } /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementFieldInput() : base("input") @@ -190,13 +190,13 @@ public HtmlElementFieldInput() } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementFieldInput(params IHtmlNode[] nodes) : this() { - Elements.AddRange(nodes); + Add(nodes); } /// diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementFieldLabel.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementFieldLabel.cs index d4529b5..7f4e656 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementFieldLabel.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementFieldLabel.cs @@ -5,10 +5,12 @@ namespace WebExpress.WebCore.WebHtml { /// /// Represents the label for a form control element (e.g. text input fields). - /// - /// /// - public class HtmlElementFieldLabel : HtmlElement, IHtmlFormularItem + /// + /// + /// + /// + public class HtmlElementFieldLabel : HtmlElement, IHtmlElementFormItem { /// /// Returns or sets the name of the input field. @@ -25,11 +27,11 @@ public string For public string Text { get => string.Join(string.Empty, Elements.Where(x => x is HtmlText).Select(x => (x as HtmlText).Value)); - set { Elements.RemoveAll(x => x is HtmlText); Elements.Insert(0, new HtmlText(value)); } + set { Clear(x => x is HtmlText); AddFirst(new HtmlText(value)); } } /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementFieldLabel() : base("label") @@ -38,7 +40,7 @@ public HtmlElementFieldLabel() } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementFieldLabel(string text) @@ -48,13 +50,13 @@ public HtmlElementFieldLabel(string text) } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementFieldLabel(params IHtmlNode[] nodes) : this() { - Elements.AddRange(nodes); + Add(nodes); } /// diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementFieldLegend.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementFieldLegend.cs index 2a6db11..a496130 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementFieldLegend.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementFieldLegend.cs @@ -3,7 +3,7 @@ namespace WebExpress.WebCore.WebHtml { /// - /// Represents a label for an
element. + /// Represents a label for an fieldset element. ///
public class HtmlElementFieldLegend : HtmlElement { @@ -13,11 +13,11 @@ public class HtmlElementFieldLegend : HtmlElement public string Text { get => string.Join("", Elements.Where(x => x is HtmlText).Select(x => (x as HtmlText).Value)); - set { Elements.Clear(); Elements.Add(new HtmlText(value)); } + set { Clear(); Add(new HtmlText(value)); } } /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementFieldLegend() : base("legend") @@ -25,7 +25,7 @@ public HtmlElementFieldLegend() } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementFieldLegend(string text) diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementFieldSelect.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementFieldSelect.cs index 8a8db3b..e089175 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementFieldSelect.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementFieldSelect.cs @@ -5,17 +5,19 @@ namespace WebExpress.WebCore.WebHtml { /// /// Represents a control that can be used to select from a range of options. + /// + /// /// - ///
- public class HtmlElementFieldSelect : HtmlElement, IHtmlFormularItem + /// + public class HtmlElementFieldSelect : HtmlElement, IHtmlElementFormItem { /// /// Returns the elements. /// - public new List Elements => base.Elements; + public new IEnumerable Elements => base.Elements; /// /// Returns or sets the name of the input field. @@ -63,7 +65,7 @@ public string OnChange } /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementFieldSelect() : base("select") @@ -72,13 +74,13 @@ public HtmlElementFieldSelect() } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementFieldSelect(params IHtmlNode[] nodes) : this() { - Elements.AddRange(nodes); + Add(nodes); } /// diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementFormDatalist.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementFormDatalist.cs index b1de468..e9f6a4c 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementFormDatalist.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementFormDatalist.cs @@ -11,10 +11,10 @@ public class HtmlElementFormDatalist : HtmlElement, IHtmlElementForm /// /// Returns the elements. /// - public new List Elements => base.Elements; + public new IEnumerable Elements => base.Elements; /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementFormDatalist() : base("datalist") @@ -22,7 +22,7 @@ public HtmlElementFormDatalist() } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementFormDatalist(string text) @@ -31,13 +31,13 @@ public HtmlElementFormDatalist(string text) } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementFormDatalist(params IHtmlNode[] nodes) : this() { - Elements.AddRange(nodes); + Add(nodes); } /// diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementFormFieldset.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementFormFieldset.cs index ad2b299..243c46a 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementFormFieldset.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementFormFieldset.cs @@ -6,7 +6,7 @@ namespace WebExpress.WebCore.WebHtml /// /// Represents a set of controls. /// - public class HtmlElementFormFieldset : HtmlElement, IHtmlFormularItem + public class HtmlElementFormFieldset : HtmlElement, IHtmlElementFormItem { /// /// Returns or sets the name of the input field. @@ -39,10 +39,10 @@ public string Form /// /// Returns the elements. /// - public new List Elements => base.Elements; + public new IEnumerable Elements => base.Elements; /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementFormFieldset() : base("fieldset") @@ -51,13 +51,13 @@ public HtmlElementFormFieldset() } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementFormFieldset(params IHtmlNode[] nodes) : this() { - Elements.AddRange(nodes); + Add(nodes); } /// diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementFormForm.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementFormForm.cs index d16bf37..54c66ce 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementFormForm.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementFormForm.cs @@ -66,10 +66,10 @@ public string Target /// /// Returns the elements. /// - public new List Elements => base.Elements; + public new IEnumerable Elements => base.Elements; /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementFormForm() : base("form") @@ -77,7 +77,7 @@ public HtmlElementFormForm() } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementFormForm(string text) @@ -86,13 +86,13 @@ public HtmlElementFormForm(string text) } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementFormForm(params IHtmlNode[] nodes) : this() { - Elements.AddRange(nodes); + Add(nodes); } /// diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementFormKeygen.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementFormKeygen.cs index 9a6e1d6..c3216ba 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementFormKeygen.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementFormKeygen.cs @@ -5,10 +5,10 @@ namespace WebExpress.WebCore.WebHtml /// /// Represents a control element for generating a pair of public and private keys and sending the public key. /// - public class HtmlElementFormKeygen : HtmlElement, IHtmlFormularItem + public class HtmlElementFormKeygen : HtmlElement, IHtmlElementFormItem { /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementFormKeygen() : base("keygen") @@ -16,13 +16,13 @@ public HtmlElementFormKeygen() } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementFormKeygen(params IHtmlNode[] nodes) : this() { - Elements.AddRange(nodes); + Add(nodes); } /// diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementFormMeter.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementFormMeter.cs index b677bb3..d769c9e 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementFormMeter.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementFormMeter.cs @@ -14,7 +14,7 @@ public class HtmlElementFormMeter : HtmlElement, IHtmlElementForm public string Text { get => string.Join(string.Empty, Elements.Where(x => x is HtmlText).Select(x => (x as HtmlText).Value)); - set { Elements.Clear(); Elements.Add(new HtmlText(value)); } + set { Clear(); Add(new HtmlText(value)); } } /// @@ -72,7 +72,7 @@ public string Optimum } /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementFormMeter() : base("meter") @@ -80,7 +80,7 @@ public HtmlElementFormMeter() } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementFormMeter(string text) diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementFormOptgroup.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementFormOptgroup.cs index 329b6e5..4050bae 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementFormOptgroup.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementFormOptgroup.cs @@ -8,16 +8,16 @@ namespace WebExpress.WebCore.WebHtml /// /// - public class HtmlElementFormOptgroup : HtmlElement, IHtmlFormularItem + public class HtmlElementFormOptgroup : HtmlElement, IHtmlElementFormItem { /// /// Returns the elements. /// - public new List Elements => base.Elements; + public new IEnumerable Elements => base.Elements; /// /// Returns or sets the label. @@ -29,7 +29,7 @@ public string Label } /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementFormOptgroup() : base("optgroup") @@ -37,13 +37,13 @@ public HtmlElementFormOptgroup() } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementFormOptgroup(params IHtmlNode[] nodes) : this() { - Elements.AddRange(nodes); + Add(nodes); } /// diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementFormOption.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementFormOption.cs index 100918d..78c988e 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementFormOption.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementFormOption.cs @@ -4,13 +4,15 @@ namespace WebExpress.WebCore.WebHtml { /// - /// Represents a selection option within an - /// - /// + /// + /// /// - /// - public class HtmlElementFormOption : HtmlElement, IHtmlFormularItem + /// + public class HtmlElementFormOption : HtmlElement, IHtmlElementFormItem { /// /// Returns or sets the text. @@ -18,7 +20,7 @@ public class HtmlElementFormOption : HtmlElement, IHtmlFormularItem public string Text { get => string.Join("", Elements.Where(x => x is HtmlText).Select(x => (x as HtmlText).Value)); - set { Elements.Clear(); Elements.Add(new HtmlText(value)); } + set { Clear(); Add(new HtmlText(value)); } } /// @@ -40,7 +42,7 @@ public bool Selected } /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementFormOption() : base("option") @@ -48,13 +50,13 @@ public HtmlElementFormOption() } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementFormOption(params IHtmlNode[] nodes) : this() { - Elements.AddRange(nodes); + Add(nodes); } /// diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementFormOutput.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementFormOutput.cs index d96c473..393e931 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementFormOutput.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementFormOutput.cs @@ -5,10 +5,10 @@ namespace WebExpress.WebCore.WebHtml /// /// Represents the result of a calculation. /// - public class HtmlElementFormOutput : HtmlElement, IHtmlFormularItem + public class HtmlElementFormOutput : HtmlElement, IHtmlElementFormItem { /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementFormOutput() : base("output") @@ -16,13 +16,13 @@ public HtmlElementFormOutput() } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementFormOutput(params IHtmlNode[] nodes) : this() { - Elements.AddRange(nodes); + Add(nodes); } /// diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementFormProgress.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementFormProgress.cs index 0041977..4b5f1dc 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementFormProgress.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementFormProgress.cs @@ -14,7 +14,7 @@ public class HtmlElementFormProgress : HtmlElement, IHtmlElementForm public string Text { get => string.Join("", Elements.Where(x => x is HtmlText).Select(x => (x as HtmlText).Value)); - set { Elements.Clear(); Elements.Add(new HtmlText(value)); } + set { Clear(); Add(new HtmlText(value)); } } /// @@ -45,7 +45,7 @@ public string Max } /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementFormProgress() : base("progress") @@ -53,7 +53,7 @@ public HtmlElementFormProgress() } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementFormProgress(string text) diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementFormTextarea.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementFormTextarea.cs index 346164c..507d17a 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementFormTextarea.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementFormTextarea.cs @@ -6,7 +6,7 @@ namespace WebExpress.WebCore.WebHtml /// /// Represents an element for multi-line text input. /// - public class HtmlElementFormTextarea : HtmlElement, IHtmlFormularItem + public class HtmlElementFormTextarea : HtmlElement, IHtmlElementFormItem { /// /// Returns or sets the name of the input field. @@ -23,7 +23,7 @@ public string Name public string Value { get => string.Join(string.Empty, Elements.Where(x => x is HtmlText).Select(x => (x as HtmlText).Value)); - set { Elements.Clear(); Elements.Add(new HtmlText(value)); } + set { Clear(); Add(new HtmlText(value)); } } /// @@ -118,7 +118,7 @@ public string Form } /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementFormTextarea() : base("textarea") @@ -126,13 +126,13 @@ public HtmlElementFormTextarea() } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementFormTextarea(params IHtmlNode[] nodes) : this() { - Elements.AddRange(nodes); + Add(nodes); } /// diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementInteractiveCommand.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementInteractiveCommand.cs index 7360c56..0acad5a 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementInteractiveCommand.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementInteractiveCommand.cs @@ -10,10 +10,10 @@ public class HtmlElementInteractiveCommand : HtmlElement, IHtmlElementInteractiv /// /// Returns the elements. /// - public new List Elements => base.Elements; + public new IEnumerable Elements => base.Elements; /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementInteractiveCommand() : base("command") @@ -21,23 +21,13 @@ public HtmlElementInteractiveCommand() } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementInteractiveCommand(params IHtmlNode[] nodes) : this() { - Elements.AddRange(nodes); - } - - /// - /// Constructor - /// - /// The content of the html element. - public HtmlElementInteractiveCommand(IEnumerable nodes) - : this() - { - base.Elements.AddRange(nodes); + Add(nodes); } } } diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementInteractiveDetails.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementInteractiveDetails.cs index 40638d5..ff30fe8 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementInteractiveDetails.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementInteractiveDetails.cs @@ -10,10 +10,10 @@ public class HtmlElementInteractiveDetails : HtmlElement, IHtmlElementInteractiv /// /// Returns the elements. /// - public new List Elements => base.Elements; + public new IEnumerable Elements => base.Elements; /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementInteractiveDetails() : base("details") @@ -21,23 +21,13 @@ public HtmlElementInteractiveDetails() } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementInteractiveDetails(params IHtmlNode[] nodes) : this() { - Elements.AddRange(nodes); - } - - /// - /// Constructor - /// - /// The content of the html element. - public HtmlElementInteractiveDetails(IEnumerable nodes) - : this() - { - base.Elements.AddRange(nodes); + Add(nodes); } } } diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementInteractiveMenu.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementInteractiveMenu.cs index 0a08db6..ac3895b 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementInteractiveMenu.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementInteractiveMenu.cs @@ -10,10 +10,10 @@ public class HtmlElementInteractiveMenu : HtmlElement, IHtmlElementInteractive /// /// Returns the elements. /// - public new List Elements => base.Elements; + public new IEnumerable Elements => base.Elements; /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementInteractiveMenu() : base("menu") @@ -21,23 +21,13 @@ public HtmlElementInteractiveMenu() } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementInteractiveMenu(params IHtmlNode[] nodes) : this() { - Elements.AddRange(nodes); - } - - /// - /// Constructor - /// - /// The content of the html element. - public HtmlElementInteractiveMenu(IEnumerable nodes) - : this() - { - base.Elements.AddRange(nodes); + Add(nodes); } } } diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementInteractiveSummary.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementInteractiveSummary.cs index ce8e7c6..b6ddb7d 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementInteractiveSummary.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementInteractiveSummary.cs @@ -3,17 +3,17 @@ namespace WebExpress.WebCore.WebHtml { /// - /// Represents a summary or legend for a specific
element. + /// Represents a summary or legend for a specific details element. ///
public class HtmlElementInteractiveSummary : HtmlElement, IHtmlElementInteractive { /// /// Returns the elements. /// - public new List Elements => base.Elements; + public new IEnumerable Elements => base.Elements; /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementInteractiveSummary() : base("summary") @@ -21,23 +21,13 @@ public HtmlElementInteractiveSummary() } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementInteractiveSummary(params IHtmlNode[] nodes) : this() { - Elements.AddRange(nodes); - } - - /// - /// Constructor - /// - /// The content of the html element. - public HtmlElementInteractiveSummary(IEnumerable nodes) - : this() - { - base.Elements.AddRange(nodes); + Add(nodes); } } } diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementMetadataBase.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementMetadataBase.cs index 219c604..e7afec7 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementMetadataBase.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementMetadataBase.cs @@ -25,7 +25,7 @@ public string Href } /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementMetadataBase() : base("base") @@ -34,7 +34,7 @@ public HtmlElementMetadataBase() } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The uri. public HtmlElementMetadataBase(string url) diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementMetadataHead.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementMetadataHead.cs index 9904000..c9341f3 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementMetadataHead.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementMetadataHead.cs @@ -10,156 +10,117 @@ namespace WebExpress.WebCore.WebHtml ///
public class HtmlElementMetadataHead : HtmlElement, IHtmlElementMetadata { + private readonly HtmlElementMetadataTitle _elementTitle = new HtmlElementMetadataTitle(); + private readonly HtmlElementMetadataBase _elementBase = new HtmlElementMetadataBase(); + private IEnumerable _elementFavicons = []; + private IEnumerable _elementStyles = []; + private IEnumerable _elementScripts = []; + private IEnumerable _elementScriptLinks = []; + private IEnumerable _elementCssLinks = []; + private IEnumerable _elementMeta = []; + /// /// Returns or sets the title. /// public string Title { - get => ElementTitle.Title; - set => ElementTitle.Title = value; + get => _elementTitle.Title; + set => _elementTitle.Title = value; } - /// - /// Returns or sets the title element. - /// - private HtmlElementMetadataTitle ElementTitle { get; set; } - /// /// Returns or sets the base. /// public string Base { - get => ElementBase.Href; - set => ElementBase.Href = value; + get => _elementBase.Href; + set => _elementBase.Href = value; } - /// - /// Returns or sets the element base. - /// - private HtmlElementMetadataBase ElementBase { get; set; } - /// /// Returns or sets the favicon. /// public IEnumerable Favicons { - get => (from x in ElementFavicons select new Favicon(x.Href, x.Type)).ToList(); + get => _elementFavicons.Select(x => new Favicon(x.Href, x.Type)); set { - ElementFavicons.Clear(); - ElementFavicons.AddRange - ( - from x in value - select new HtmlElementMetadataLink() - { - Href = x.Url, - Rel = "icon", - Type = x.Mediatype != TypeFavicon.Default ? x.GetMediatyp() : "" - }); + _elementFavicons = value.Select(x => new HtmlElementMetadataLink() + { + Href = x.Url, + Rel = "icon", + Type = x.Mediatype != TypeFavicon.Default ? x.GetMediatyp() : "" + }); } } - /// - /// Returns or sets the favicon link. - /// - private List ElementFavicons { get; set; } - /// /// Returns or sets the internal stylesheet. /// public IEnumerable Styles { - get => (from x in ElementStyles select x.Code).ToList(); - set { ElementStyles.Clear(); ElementStyles.AddRange(from x in value select new HtmlElementMetadataStyle(x)); } + get => _elementStyles.Select(x => x.Code); + set { _elementStyles = value.Select(x => new HtmlElementMetadataStyle(x)); } } - /// - /// Returns or sets the style elements. - /// - private List ElementStyles { get; set; } - /// /// Returns or sets the scripts. /// public IEnumerable Scripts { - get => (from x in ElementScripts select x.Code).ToList(); - set { ElementScripts.Clear(); ElementScripts.AddRange(from x in value select new HtmlElementScriptingScript(x)); } + get => _elementScriptLinks.Select(x => x.Code); + set { _elementScripts = value.Select(x => new HtmlElementScriptingScript(x)); } } - /// - /// Returns or sets the script elements. - /// - private List ElementScripts { get; set; } - /// /// Returns or sets the text/javascript. /// public IEnumerable ScriptLinks { - get => (from x in ElementScriptLinks select x.Src).ToList(); + get => _elementScriptLinks.Select(x => x.Src); set { - ElementScriptLinks.Clear(); ElementScriptLinks.AddRange(from x in value - select new HtmlElementScriptingScript() { Language = "javascript", Src = x, Type = "text/javascript" }); + _elementScriptLinks = value.Select(x => new HtmlElementScriptingScript() + { + Language = "javascript", + Src = x, + Type = "text/javascript" + }); } } - /// - /// Returns or sets the external scripts. - /// - private List ElementScriptLinks { get; set; } - /// /// Returns or sets the internal stylesheet. /// public IEnumerable CssLinks { - get => (from x in ElementCssLinks select x.Href).ToList(); + get => _elementCssLinks.Select(x => x.Href); set { - ElementCssLinks.Clear(); ElementCssLinks.AddRange(from x in value - select new HtmlElementMetadataLink() { Rel = "stylesheet", Href = x, Type = "text/css" }); + _elementCssLinks = value.Select(x => new HtmlElementMetadataLink() + { + Rel = "stylesheet", + Href = x, + Type = "text/css" + }); } } - /// - /// Returns or sets the css link. - /// - private List ElementCssLinks { get; set; } - /// /// Returns or sets the metadata. /// public IEnumerable> Meta { - get => (from x in ElementMeta select new KeyValuePair(x.Key, x.Value)).ToList(); - set - { - ElementMeta.Clear(); ElementMeta.AddRange(from x in value - select new HtmlElementMetadataMeta(x.Key, x.Value)); - } + get => _elementMeta.Select(x => new KeyValuePair(x.Key, x.Value)); + set { _elementMeta = value.Select(x => new HtmlElementMetadataMeta(x.Key, x.Value)); } } /// - /// Returns or sets the metadata elements. - /// - private List ElementMeta { get; set; } - - /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementMetadataHead() : base("head") { - ElementTitle = new HtmlElementMetadataTitle(); - ElementBase = new HtmlElementMetadataBase(); - ElementFavicons = new List(); - ElementStyles = new List(); - ElementScripts = new List(); - ElementScriptLinks = new List(); - ElementCssLinks = new List(); - ElementMeta = new List(); } /// @@ -173,7 +134,7 @@ public override void ToString(StringBuilder builder, int deep) if (!string.IsNullOrWhiteSpace(Title)) { - ElementTitle.ToString(builder, deep + 1); + _elementTitle.ToString(builder, deep + 1); } if (!string.IsNullOrWhiteSpace(Base)) @@ -181,32 +142,32 @@ public override void ToString(StringBuilder builder, int deep) //ElementBase.ToString(builder, deep + 1); } - foreach (var v in ElementFavicons) + foreach (var v in _elementFavicons) { v.ToString(builder, deep + 1); } - foreach (var v in ElementStyles) + foreach (var v in _elementStyles) { v.ToString(builder, deep + 1); } - foreach (var v in ElementScriptLinks) + foreach (var v in _elementScriptLinks) { v.ToString(builder, deep + 1); } - foreach (var v in ElementScripts) + foreach (var v in _elementScripts) { v.ToString(builder, deep + 1); } - foreach (var v in ElementCssLinks) + foreach (var v in _elementCssLinks) { v.ToString(builder, deep + 1); } - foreach (var v in ElementMeta) + foreach (var v in _elementMeta) { v.ToString(builder, deep + 1); } diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementMetadataLink.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementMetadataLink.cs index c47606b..7114047 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementMetadataLink.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementMetadataLink.cs @@ -33,7 +33,7 @@ public string Type } /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementMetadataLink() : base("link", false) diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementMetadataMeta.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementMetadataMeta.cs index d64440c..9ba147b 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementMetadataMeta.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementMetadataMeta.cs @@ -22,7 +22,7 @@ public string Value } /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementMetadataMeta() : base("meta") @@ -30,7 +30,7 @@ public HtmlElementMetadataMeta() } /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementMetadataMeta(string key) : this() @@ -40,7 +40,7 @@ public HtmlElementMetadataMeta(string key) } /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementMetadataMeta(string key, string value) : this() diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementMetadataStyle.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementMetadataStyle.cs index be11945..d1c4f79 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementMetadataStyle.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementMetadataStyle.cs @@ -13,7 +13,7 @@ public class HtmlElementMetadataStyle : HtmlElement, IHtmlElementMetadata public string Code { get; set; } /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementMetadataStyle() : base("style") @@ -21,7 +21,7 @@ public HtmlElementMetadataStyle() } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The text. public HtmlElementMetadataStyle(string code) diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementMetadataTitle.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementMetadataTitle.cs index 56f3564..d1dda04 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementMetadataTitle.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementMetadataTitle.cs @@ -14,7 +14,7 @@ public class HtmlElementMetadataTitle : HtmlElement, IHtmlElementMetadata public string Title { get; set; } /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementMetadataTitle() : base("title") @@ -22,7 +22,7 @@ public HtmlElementMetadataTitle() } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The title. public HtmlElementMetadataTitle(string title) diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementMultimediaArea.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementMultimediaArea.cs index df805a8..34519e7 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementMultimediaArea.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementMultimediaArea.cs @@ -1,17 +1,16 @@ namespace WebExpress.WebCore.WebHtml { /// - /// Represents an image map in conjunction with the element. + /// Represents an image map in conjunction with the map element. /// public class HtmlElementMultimediaArea : HtmlElement, IHtmlElementMultimedia { /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementMultimediaArea() : base("area", false) { - } } } diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementMultimediaAudio.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementMultimediaAudio.cs index 7b9e5d1..89c8482 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementMultimediaAudio.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementMultimediaAudio.cs @@ -15,7 +15,7 @@ public string Src } /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementMultimediaAudio() : base("audio", false) diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementMultimediaImg.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementMultimediaImg.cs index fa9b055..5e27173 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementMultimediaImg.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementMultimediaImg.cs @@ -62,7 +62,7 @@ public string Target } /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementMultimediaImg() : base("img", false) diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementMultimediaMap.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementMultimediaMap.cs index 681306a..4075919 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementMultimediaMap.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementMultimediaMap.cs @@ -1,12 +1,12 @@ namespace WebExpress.WebCore.WebHtml { /// - /// Represents an image map in conjunction with the element. + /// Represents an image map in conjunction with the area element. /// public class HtmlElementMultimediaMap : HtmlElement, IHtmlElementMultimedia { /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementMultimediaMap() : base("map", false) diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementMultimediaMath.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementMultimediaMath.cs index cc47c4d..ba0ced0 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementMultimediaMath.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementMultimediaMath.cs @@ -10,10 +10,10 @@ public class HtmlElementMultimediaMath : HtmlElement, IHtmlElementMultimedia /// /// Returns the elements. /// - public new List Elements => base.Elements; + public new IEnumerable Elements => base.Elements; /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementMultimediaMath() : base("math") @@ -21,23 +21,13 @@ public HtmlElementMultimediaMath() } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementMultimediaMath(params IHtmlNode[] nodes) : this() { - Elements.AddRange(nodes); - } - - /// - /// Constructor - /// - /// The content of the html element. - public HtmlElementMultimediaMath(IEnumerable nodes) - : this() - { - base.Elements.AddRange(nodes); + Add(nodes); } } } diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementMultimediaSvg.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementMultimediaSvg.cs index 1dc5222..1a904a1 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementMultimediaSvg.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementMultimediaSvg.cs @@ -11,7 +11,7 @@ public class HtmlElementMultimediaSvg : HtmlElement, IHtmlElementMultimedia /// /// Returns the elements. /// - public new List Elements => base.Elements; + public new IEnumerable Elements => base.Elements; /// /// Returns or sets the width. @@ -41,7 +41,7 @@ public string Target } /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementMultimediaSvg() : base("svg") @@ -49,23 +49,13 @@ public HtmlElementMultimediaSvg() } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementMultimediaSvg(params IHtmlNode[] nodes) : this() { - Elements.AddRange(nodes); - } - - /// - /// Constructor - /// - /// The content of the html element. - public HtmlElementMultimediaSvg(IEnumerable nodes) - : this() - { - base.Elements.AddRange(nodes); + Add(nodes); } } } diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementMultimediaTrack.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementMultimediaTrack.cs index debba82..99908fc 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementMultimediaTrack.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementMultimediaTrack.cs @@ -1,12 +1,12 @@ namespace WebExpress.WebCore.WebHtml { /// - /// Allows you to specify additional media tracks (e.g. subtitles) for elements such as public class HtmlElementMultimediaTrack : HtmlElement, IHtmlElementMultimedia { /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementMultimediaTrack() : base("track", false) diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementMultimediaVideo.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementMultimediaVideo.cs index 5ade74a..77e8181 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementMultimediaVideo.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementMultimediaVideo.cs @@ -15,7 +15,7 @@ public string Src } /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementMultimediaVideo() : base("video", false) diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementRootHtml.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementRootHtml.cs index 754d4c7..9ea0eb1 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementRootHtml.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementRootHtml.cs @@ -19,7 +19,7 @@ public class HtmlElementRootHtml : HtmlElement, IHtmlElementRoot public HtmlElementSectionBody Body { get; private set; } /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementRootHtml() : base("html") diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementScriptingCanvas.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementScriptingCanvas.cs index 9ac25ab..af93b06 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementScriptingCanvas.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementScriptingCanvas.cs @@ -27,7 +27,7 @@ public int Height } /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementScriptingCanvas() : base("canvas", false) diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementScriptingNoscript.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementScriptingNoscript.cs index 725dfa3..7832e49 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementScriptingNoscript.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementScriptingNoscript.cs @@ -10,10 +10,10 @@ public class HtmlElementScriptingNoscript : HtmlElement, IHtmlElementScripting /// /// Returns the elements. /// - public new List Elements => base.Elements; + public new IEnumerable Elements => base.Elements; /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementScriptingNoscript() : base("span") @@ -21,23 +21,13 @@ public HtmlElementScriptingNoscript() } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementScriptingNoscript(params IHtmlNode[] nodes) : this() { - Elements.AddRange(nodes); - } - - /// - /// Constructor - /// - /// The content of the html element. - public HtmlElementScriptingNoscript(IEnumerable nodes) - : this() - { - base.Elements.AddRange(nodes); + Add(nodes); } } } diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementScriptingScript.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementScriptingScript.cs index abec94d..774a7e0 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementScriptingScript.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementScriptingScript.cs @@ -40,7 +40,7 @@ public string Src } /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementScriptingScript() : base("script") @@ -49,7 +49,7 @@ public HtmlElementScriptingScript() } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The text. public HtmlElementScriptingScript(string code) diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementSectionAddress.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementSectionAddress.cs index 44f2093..9b6ad27 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementSectionAddress.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementSectionAddress.cs @@ -10,10 +10,10 @@ public class HtmlElementSectionAddress : HtmlElement, IHtmlElementSection /// /// Returns the elements. /// - public new List Elements => base.Elements; + public new IEnumerable Elements => base.Elements; /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementSectionAddress() : base("address") @@ -21,23 +21,13 @@ public HtmlElementSectionAddress() } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementSectionAddress(params IHtmlNode[] nodes) : this() { - Elements.AddRange(nodes); - } - - /// - /// Constructor - /// - /// The content of the html element. - public HtmlElementSectionAddress(IEnumerable nodes) - : this() - { - base.Elements.AddRange(nodes); + Add(nodes); } } } diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementSectionArticle.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementSectionArticle.cs index 5e51121..ec7c9d5 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementSectionArticle.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementSectionArticle.cs @@ -10,10 +10,10 @@ public class HtmlElementSectionArticle : HtmlElement, IHtmlElementSection /// /// Returns the elements. /// - public new List Elements => base.Elements; + public new IEnumerable Elements => base.Elements; /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementSectionArticle() : base("article") @@ -21,23 +21,13 @@ public HtmlElementSectionArticle() } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementSectionArticle(params IHtmlNode[] nodes) : this() { - Elements.AddRange(nodes); - } - - /// - /// Constructor - /// - /// The content of the html element. - public HtmlElementSectionArticle(IEnumerable nodes) - : this() - { - base.Elements.AddRange(nodes); + Add(nodes); } } } diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementSectionAside.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementSectionAside.cs index 9a372bb..3b2528c 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementSectionAside.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementSectionAside.cs @@ -10,10 +10,10 @@ public class HtmlElementSectionAside : HtmlElement, IHtmlElementSection /// /// Returns the elements. /// - public new List Elements => base.Elements; + public new IEnumerable Elements => base.Elements; /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementSectionAside() : base("aside") @@ -21,23 +21,13 @@ public HtmlElementSectionAside() } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementSectionAside(params IHtmlNode[] nodes) : this() { - Elements.AddRange(nodes); - } - - /// - /// Constructor - /// - /// The content of the html element. - public HtmlElementSectionAside(IEnumerable nodes) - : this() - { - base.Elements.AddRange(nodes); + Add(nodes); } } } diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementSectionBody.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementSectionBody.cs index 7823765..08ecf45 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementSectionBody.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementSectionBody.cs @@ -5,14 +5,14 @@ namespace WebExpress.WebCore.WebHtml { /// - /// Represents the main content of an HTML document. Each document can contain only one element. + /// Represents the main content of an HTML document. Each document can contain only one body element. /// public class HtmlElementSectionBody : HtmlElement, IHtmlElementSection { /// /// Returns the elements. /// - public new List Elements => base.Elements; + public new IEnumerable Elements => base.Elements; /// /// Returns or sets the script elements. @@ -44,22 +44,22 @@ public List ScriptLinks private List ElementScriptLinks { get; set; } /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementSectionBody() : base("body") { - ElementScriptLinks = new List(); + ElementScriptLinks = []; } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementSectionBody(params IHtmlNode[] nodes) : this() { - Elements.AddRange(nodes); + Add(nodes); } /// diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementSectionFooter.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementSectionFooter.cs index 7bcb03c..6499a17 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementSectionFooter.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementSectionFooter.cs @@ -11,7 +11,7 @@ public class HtmlElementSectionFooter : HtmlElement, IHtmlElementSection /// /// Returns the elements. /// - public new List Elements => base.Elements; + public new IEnumerable Elements => base.Elements; /// /// Returns or sets the text. @@ -19,11 +19,11 @@ public class HtmlElementSectionFooter : HtmlElement, IHtmlElementSection public string Text { get => string.Join("", Elements.Where(x => x is HtmlText).Select(x => (x as HtmlText).Value)); - set { Elements.Clear(); Elements.Add(new HtmlText(value)); } + set { Clear(); Add(new HtmlText(value)); } } /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementSectionFooter() : base("footer") @@ -31,7 +31,7 @@ public HtmlElementSectionFooter() } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementSectionFooter(string text) @@ -41,23 +41,13 @@ public HtmlElementSectionFooter(string text) } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementSectionFooter(params IHtmlNode[] nodes) : this() { - Elements.AddRange(nodes); - } - - /// - /// Constructor - /// - /// The content of the html element. - public HtmlElementSectionFooter(IEnumerable nodes) - : this() - { - Elements.AddRange(nodes); + Add(nodes); } } } diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementSectionH1.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementSectionH1.cs index 579471f..1f291b4 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementSectionH1.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementSectionH1.cs @@ -14,11 +14,11 @@ public class HtmlElementSectionH1 : HtmlElement, IHtmlElementSection public string Text { get => string.Join("", Elements.Where(x => x is HtmlText).Select(x => (x as HtmlText).Value)); - set { Elements.Clear(); Elements.Add(new HtmlText(value)); } + set { Clear(); Add(new HtmlText(value)); } } /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementSectionH1() : base("h1") @@ -27,7 +27,7 @@ public HtmlElementSectionH1() } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementSectionH1(string text) @@ -37,13 +37,13 @@ public HtmlElementSectionH1(string text) } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementSectionH1(params IHtmlNode[] nodes) : this() { - Elements.AddRange(nodes); + Add(nodes); } /// diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementSectionH2.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementSectionH2.cs index 7fdec3b..f5e5036 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementSectionH2.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementSectionH2.cs @@ -14,11 +14,11 @@ public class HtmlElementSectionH2 : HtmlElement, IHtmlElementSection public string Text { get => string.Join("", Elements.Where(x => x is HtmlText).Select(x => (x as HtmlText).Value)); - set { Elements.Clear(); Elements.Add(new HtmlText(value)); } + set { Clear(); Add(new HtmlText(value)); } } /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementSectionH2() : base("h2") @@ -27,7 +27,7 @@ public HtmlElementSectionH2() } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementSectionH2(string text) @@ -37,13 +37,13 @@ public HtmlElementSectionH2(string text) } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementSectionH2(params IHtmlNode[] nodes) : this() { - Elements.AddRange(nodes); + Add(nodes); } /// diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementSectionH3.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementSectionH3.cs index b226a40..78d2c5a 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementSectionH3.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementSectionH3.cs @@ -14,11 +14,11 @@ public class HtmlElementSectionH3 : HtmlElement, IHtmlElementSection public string Text { get => string.Join("", Elements.Where(x => x is HtmlText).Select(x => (x as HtmlText).Value)); - set { Elements.Clear(); Elements.Add(new HtmlText(value)); } + set { Clear(); Add(new HtmlText(value)); } } /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementSectionH3() : base("h3") @@ -27,7 +27,7 @@ public HtmlElementSectionH3() } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementSectionH3(string text) @@ -37,13 +37,13 @@ public HtmlElementSectionH3(string text) } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementSectionH3(params IHtmlNode[] nodes) : this() { - Elements.AddRange(nodes); + Add(nodes); } /// diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementSectionH4.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementSectionH4.cs index b522df3..d92e0e5 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementSectionH4.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementSectionH4.cs @@ -14,11 +14,11 @@ public class HtmlElementSectionH4 : HtmlElement, IHtmlElementSection public string Text { get => string.Join("", Elements.Where(x => x is HtmlText).Select(x => (x as HtmlText).Value)); - set { Elements.Clear(); Elements.Add(new HtmlText(value)); } + set { Clear(); Add(new HtmlText(value)); } } /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementSectionH4() : base("h4") @@ -27,7 +27,7 @@ public HtmlElementSectionH4() } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementSectionH4(string text) @@ -37,13 +37,13 @@ public HtmlElementSectionH4(string text) } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementSectionH4(params IHtmlNode[] nodes) : this() { - Elements.AddRange(nodes); + Add(nodes); } /// diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementSectionH5.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementSectionH5.cs index c91e6db..15728b4 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementSectionH5.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementSectionH5.cs @@ -14,11 +14,11 @@ public class HtmlElementSectionH5 : HtmlElement, IHtmlElementSection public string Text { get => string.Join("", Elements.Where(x => x is HtmlText).Select(x => (x as HtmlText).Value)); - set { Elements.Clear(); Elements.Add(new HtmlText(value)); } + set { Clear(); Add(new HtmlText(value)); } } /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementSectionH5() : base("h5") @@ -27,7 +27,7 @@ public HtmlElementSectionH5() } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementSectionH5(string text) @@ -37,13 +37,13 @@ public HtmlElementSectionH5(string text) } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementSectionH5(params IHtmlNode[] nodes) : this() { - Elements.AddRange(nodes); + Add(nodes); } /// diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementSectionH6.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementSectionH6.cs index e49196c..5feac4b 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementSectionH6.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementSectionH6.cs @@ -14,11 +14,11 @@ public class HtmlElementSectionH6 : HtmlElement, IHtmlElementSection public string Text { get => string.Join("", Elements.Where(x => x is HtmlText).Select(x => (x as HtmlText).Value)); - set { Elements.Clear(); Elements.Add(new HtmlText(value)); } + set { Clear(); Add(new HtmlText(value)); } } /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementSectionH6() : base("h6") @@ -27,7 +27,7 @@ public HtmlElementSectionH6() } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementSectionH6(string text) @@ -37,13 +37,13 @@ public HtmlElementSectionH6(string text) } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementSectionH6(params IHtmlNode[] nodes) : this() { - Elements.AddRange(nodes); + Add(nodes); } /// diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementSectionHeader.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementSectionHeader.cs index 4fb0d14..8315f35 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementSectionHeader.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementSectionHeader.cs @@ -11,10 +11,10 @@ public class HtmlElementSectionHeader : HtmlElement, IHtmlElementSection /// /// Returns the elements. /// - public new List Elements => base.Elements; + public new IEnumerable Elements => base.Elements; /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementSectionHeader() : base("header") @@ -23,23 +23,13 @@ public HtmlElementSectionHeader() } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementSectionHeader(params IHtmlNode[] nodes) : this() { - Elements.AddRange(nodes); - } - - /// - /// Constructor - /// - /// The content of the html element. - public HtmlElementSectionHeader(IEnumerable nodes) - : this() - { - base.Elements.AddRange(nodes); + Add(nodes); } } } diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementSectionMain.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementSectionMain.cs index 21e64dc..07c9575 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementSectionMain.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementSectionMain.cs @@ -3,17 +3,17 @@ namespace WebExpress.WebCore.WebHtml { /// - /// Represents the main content of the page. Only one
element per page is allowed. + /// Represents the main content of the page. Only one main element per page is allowed. ///
public class HtmlElementSectionMain : HtmlElement, IHtmlElementSection { /// /// Returns the elements. /// - public new List Elements => base.Elements; + public new IEnumerable Elements => base.Elements; /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementSectionMain() : base("main") @@ -22,23 +22,13 @@ public HtmlElementSectionMain() } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementSectionMain(params IHtmlNode[] nodes) : this() { - Elements.AddRange(nodes); - } - - /// - /// Constructor - /// - /// The content of the html element. - public HtmlElementSectionMain(IEnumerable nodes) - : this() - { - base.Elements.AddRange(nodes); + Add(nodes); } } } diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementSectionNav.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementSectionNav.cs index c57d478..737d2fa 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementSectionNav.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementSectionNav.cs @@ -10,10 +10,10 @@ public class HtmlElementSectionNav : HtmlElement /// /// Returns the elements. /// - public new List Elements => base.Elements; + public new IEnumerable Elements => base.Elements; /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementSectionNav() : base("nav") @@ -21,23 +21,13 @@ public HtmlElementSectionNav() } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementSectionNav(params IHtmlNode[] nodes) : this() { - Elements.AddRange(nodes); - } - - /// - /// Constructor - /// - /// The content of the html element. - public HtmlElementSectionNav(IEnumerable nodes) - : this() - { - base.Elements.AddRange(nodes); + Add(nodes); } } } diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementSectionSection.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementSectionSection.cs index 3c3ca21..406e4b9 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementSectionSection.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementSectionSection.cs @@ -10,10 +10,10 @@ public class HtmlElementSectionSection : HtmlElement, IHtmlElementSection /// /// Returns the elements. /// - public new List Elements => base.Elements; + public new IEnumerable Elements => base.Elements; /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementSectionSection() : base("section") @@ -21,23 +21,13 @@ public HtmlElementSectionSection() } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementSectionSection(params IHtmlNode[] nodes) : this() { - Elements.AddRange(nodes); - } - - /// - /// Constructor - /// - /// The content of the html element. - public HtmlElementSectionSection(IEnumerable nodes) - : this() - { - base.Elements.AddRange(nodes); + Add(nodes); } } } diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementTableCaption.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementTableCaption.cs index 50c8cf3..88a60e1 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementTableCaption.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementTableCaption.cs @@ -14,11 +14,11 @@ public class HtmlElementTableCaption : HtmlElement, IHtmlElementTable public string Text { get => string.Join("", Elements.Where(x => x is HtmlText).Select(x => (x as HtmlText).Value)); - set { Elements.Clear(); Elements.Add(new HtmlText(value)); } + set { Clear(); Add(new HtmlText(value)); } } /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementTableCaption() : base("caption") @@ -26,7 +26,7 @@ public HtmlElementTableCaption() } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementTableCaption(string text) @@ -36,13 +36,13 @@ public HtmlElementTableCaption(string text) } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementTableCaption(params IHtmlNode[] nodes) : this() { - Elements.AddRange(nodes); + Add(nodes); } /// diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementTableCol.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementTableCol.cs index e50b906..77eda4b 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementTableCol.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementTableCol.cs @@ -6,7 +6,7 @@ public class HtmlElementTableCol : HtmlElement, IHtmlElementTable { /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementTableCol() : base("col") diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementTableColgroup.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementTableColgroup.cs index 33568e2..57addab 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementTableColgroup.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementTableColgroup.cs @@ -6,7 +6,7 @@ public class HtmlElementTableColgroup : HtmlElement, IHtmlElementTable { /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementTableColgroup() : base("colgroup") diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementTableTable.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementTableTable.cs index 7f54c2b..00afad9 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementTableTable.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementTableTable.cs @@ -19,7 +19,7 @@ public class HtmlElementTableTable : HtmlElement, IHtmlElementTable public List Rows { get; private set; } /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementTableTable() : base("table") @@ -40,7 +40,7 @@ public override void ToString(StringBuilder builder, int deep) var column = new HtmlElementTableThead(Columns); column.ToString(builder, deep + 1); - var body = new HtmlElementTableTbody(Rows); + var body = new HtmlElementTableTbody(Rows.ToArray()); body.ToString(builder, deep + 1); ToPostString(builder, deep); diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementTableTbody.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementTableTbody.cs index d7559bc..179e159 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementTableTbody.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementTableTbody.cs @@ -1,6 +1,4 @@ -using System.Collections.Generic; - -namespace WebExpress.WebCore.WebHtml +namespace WebExpress.WebCore.WebHtml { /// /// Represents the columns that contain the actual data of a table. @@ -8,7 +6,7 @@ namespace WebExpress.WebCore.WebHtml public class HtmlElementTableTbody : HtmlElement, IHtmlElementTable { /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementTableTbody() : base("tbody") @@ -16,23 +14,13 @@ public HtmlElementTableTbody() } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementTableTbody(params IHtmlNode[] nodes) : this() { - Elements.AddRange(nodes); - } - - /// - /// Constructor - /// - /// The content of the html element. - public HtmlElementTableTbody(IEnumerable nodes) - : this() - { - Elements.AddRange(nodes); + Add(nodes); } } } diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementTableTd.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementTableTd.cs index 17b2779..c41d88e 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementTableTd.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementTableTd.cs @@ -1,6 +1,4 @@ -using System.Collections.Generic; - -namespace WebExpress.WebCore.WebHtml +namespace WebExpress.WebCore.WebHtml { /// /// Represents a single table cell. @@ -8,7 +6,7 @@ namespace WebExpress.WebCore.WebHtml public class HtmlElementTableTd : HtmlElement, IHtmlElementTable { /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementTableTd() : base("td") @@ -16,33 +14,13 @@ public HtmlElementTableTd() } /// - /// Constructor - /// - /// The content of the html element. - public HtmlElementTableTd(IHtmlNode node) - : this() - { - Elements.Add(node); - } - - /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementTableTd(params IHtmlNode[] nodes) : this() { - Elements.AddRange(nodes); - } - - /// - /// Constructor - /// - /// The content of the html element. - public HtmlElementTableTd(IEnumerable nodes) - : this() - { - Elements.AddRange(nodes); + Add(nodes); } } } diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementTableTfoot.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementTableTfoot.cs index 6a3c1f2..578ebe9 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementTableTfoot.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementTableTfoot.cs @@ -1,6 +1,4 @@ -using System.Collections.Generic; - -namespace WebExpress.WebCore.WebHtml +namespace WebExpress.WebCore.WebHtml { /// /// Represents the group of table rows that contain the summaries of the table columns. @@ -8,7 +6,7 @@ namespace WebExpress.WebCore.WebHtml public class HtmlElementTableTfoot : HtmlElement, IHtmlElementTable { /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementTableTfoot() : base("tfoot") @@ -16,23 +14,13 @@ public HtmlElementTableTfoot() } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementTableTfoot(params IHtmlNode[] nodes) : this() { - Elements.AddRange(nodes); - } - - /// - /// Constructor - /// - /// The content of the html element. - public HtmlElementTableTfoot(IEnumerable nodes) - : this() - { - Elements.AddRange(nodes); + Add(nodes); } } } diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementTableTh.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementTableTh.cs index 8f5623c..b09f878 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementTableTh.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementTableTh.cs @@ -1,6 +1,4 @@ -using System.Collections.Generic; - -namespace WebExpress.WebCore.WebHtml +namespace WebExpress.WebCore.WebHtml { /// /// Represents a table cell with a label. @@ -8,7 +6,7 @@ namespace WebExpress.WebCore.WebHtml public class HtmlElementTableTh : HtmlElement, IHtmlElementTable { /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementTableTh() : base("th") @@ -16,23 +14,13 @@ public HtmlElementTableTh() } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementTableTh(params IHtmlNode[] nodes) : this() { - Elements.AddRange(nodes); - } - - /// - /// Constructor - /// - /// The content of the html element. - public HtmlElementTableTh(List nodes) - : this() - { - Elements.AddRange(nodes); + Add(nodes); } } } diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementTableThead.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementTableThead.cs index fa56102..4b12ebd 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementTableThead.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementTableThead.cs @@ -1,6 +1,4 @@ -using System.Collections.Generic; - -namespace WebExpress.WebCore.WebHtml +namespace WebExpress.WebCore.WebHtml { /// /// Represents the group of table rows that contain the labels of the table columns. @@ -8,7 +6,7 @@ namespace WebExpress.WebCore.WebHtml public class HtmlElementTableThead : HtmlElement, IHtmlElementTable { /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementTableThead() : base("thead") @@ -16,23 +14,13 @@ public HtmlElementTableThead() } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementTableThead(params IHtmlNode[] nodes) : this() { - Elements.AddRange(nodes); - } - - /// - /// Constructor - /// - /// The content of the html element. - public HtmlElementTableThead(IEnumerable nodes) - : this() - { - Elements.AddRange(nodes); + Add(nodes); } } } diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementTableTr.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementTableTr.cs index 3010a18..68be008 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementTableTr.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementTableTr.cs @@ -1,6 +1,4 @@ -using System.Collections.Generic; - -namespace WebExpress.WebCore.WebHtml +namespace WebExpress.WebCore.WebHtml { /// /// Represents a row of table cells. @@ -8,7 +6,7 @@ namespace WebExpress.WebCore.WebHtml public class HtmlElementTableTr : HtmlElement, IHtmlElementTable { /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementTableTr() : base("tr") @@ -16,23 +14,13 @@ public HtmlElementTableTr() } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementTableTr(params IHtmlNode[] nodes) : this() { - Elements.AddRange(nodes); - } - - /// - /// Constructor - /// - /// The content of the html element. - public HtmlElementTableTr(IEnumerable nodes) - : this() - { - Elements.AddRange(nodes); + Add(nodes); } } } diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementTextContentBlockquote.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementTextContentBlockquote.cs index f8905ae..60fbaf3 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementTextContentBlockquote.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementTextContentBlockquote.cs @@ -10,10 +10,10 @@ public class HtmlElementTextContentBlockquote : HtmlElement, IHtmlElementTextCon /// /// Returns the elements. /// - public new List Elements => base.Elements; + public new IEnumerable Elements => base.Elements; /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementTextContentBlockquote() : base("blockquote") @@ -21,23 +21,13 @@ public HtmlElementTextContentBlockquote() } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementTextContentBlockquote(params IHtmlNode[] nodes) : this() { - Elements.AddRange(nodes); - } - - /// - /// Constructor - /// - /// The content of the html element. - public HtmlElementTextContentBlockquote(IEnumerable nodes) - : this() - { - base.Elements.AddRange(nodes); + Add(nodes); } } } diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementTextContentDd.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementTextContentDd.cs index 3181f98..99d6990 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementTextContentDd.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementTextContentDd.cs @@ -3,17 +3,17 @@ namespace WebExpress.WebCore.WebHtml { /// - /// Represents the definition of the term(s) specified in the immediately preceding
element. + /// Represents the definition of the term(s) specified in the immediately preceding dt element. ///
public class HtmlElementTextContentDd : HtmlElement, IHtmlElementTextContent { /// /// Returns the elements. /// - public new List Elements => base.Elements; + public new IEnumerable Elements => base.Elements; /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementTextContentDd() : base("dd") @@ -22,23 +22,13 @@ public HtmlElementTextContentDd() } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementTextContentDd(params IHtmlNode[] nodes) : this() { - Elements.AddRange(nodes); - } - - /// - /// Constructor - /// - /// The content of the html element. - public HtmlElementTextContentDd(IEnumerable nodes) - : this() - { - base.Elements.AddRange(nodes); + Add(nodes); } } } diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementTextContentDiv.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementTextContentDiv.cs index 4d14971..fbe6fc0 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementTextContentDiv.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementTextContentDiv.cs @@ -10,10 +10,10 @@ public class HtmlElementTextContentDiv : HtmlElement /// /// Returns the elements. /// - public new List Elements => base.Elements; + public new IEnumerable Elements => base.Elements; /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementTextContentDiv() : base("div") @@ -22,23 +22,13 @@ public HtmlElementTextContentDiv() } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementTextContentDiv(params IHtmlNode[] nodes) : this() { - Elements.AddRange(nodes); - } - - /// - /// Constructor - /// - /// The content of the html element. - public HtmlElementTextContentDiv(IEnumerable nodes) - : this() - { - base.Elements.AddRange(nodes); + Add(nodes); } } } diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementTextContentDl.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementTextContentDl.cs index 495059d..0cc5333 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementTextContentDl.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementTextContentDl.cs @@ -10,10 +10,10 @@ public class HtmlElementTextContentDl : HtmlElement, IHtmlElementTextContent /// /// Returns the elements. /// - public new List Elements => base.Elements; + public new IEnumerable Elements => base.Elements; /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementTextContentDl() : base("dl") @@ -22,23 +22,13 @@ public HtmlElementTextContentDl() } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementTextContentDl(params IHtmlNode[] nodes) : this() { - Elements.AddRange(nodes); - } - - /// - /// Constructor - /// - /// The content of the html element. - public HtmlElementTextContentDl(IEnumerable nodes) - : this() - { - base.Elements.AddRange(nodes); + Add(nodes); } } } diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementTextContentDt.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementTextContentDt.cs index a9e9ee8..b199b8d 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementTextContentDt.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementTextContentDt.cs @@ -3,17 +3,17 @@ namespace WebExpress.WebCore.WebHtml { /// - /// Represents a term described in the following
element. + /// Represents a term described in the following dt element. ///
public class HtmlElementTextContentDt : HtmlElement, IHtmlElementTextContent { /// /// Returns the elements. /// - public new List Elements => base.Elements; + public new IEnumerable Elements => base.Elements; /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementTextContentDt() : base("dt") @@ -21,23 +21,13 @@ public HtmlElementTextContentDt() } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementTextContentDt(params IHtmlNode[] nodes) : this() { - Elements.AddRange(nodes); - } - - /// - /// Constructor - /// - /// The content of the html element. - public HtmlElementTextContentDt(IEnumerable nodes) - : this() - { - base.Elements.AddRange(nodes); + Add(nodes); } } } diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementTextContentFigcaption.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementTextContentFigcaption.cs index 157eca9..6ed56d2 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementTextContentFigcaption.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementTextContentFigcaption.cs @@ -10,10 +10,10 @@ public class HtmlElementTextContentFigcaption : HtmlElement, IHtmlElementTextCon /// /// Returns the elements. /// - public new List Elements => base.Elements; + public new IEnumerable Elements => base.Elements; /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementTextContentFigcaption() : base("figcaption") @@ -21,23 +21,13 @@ public HtmlElementTextContentFigcaption() } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementTextContentFigcaption(params IHtmlNode[] nodes) : this() { - Elements.AddRange(nodes); - } - - /// - /// Constructor - /// - /// The content of the html element. - public HtmlElementTextContentFigcaption(IEnumerable nodes) - : this() - { - base.Elements.AddRange(nodes); + Add(nodes); } } } diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementTextContentFigure.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementTextContentFigure.cs index c88f9c0..a626abd 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementTextContentFigure.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementTextContentFigure.cs @@ -10,10 +10,10 @@ public class HtmlElementTextContentFigure : HtmlElement, IHtmlElementTextContent /// /// Returns the elements. /// - public new List Elements => base.Elements; + public new IEnumerable Elements => base.Elements; /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementTextContentFigure() : base("figure") @@ -21,23 +21,13 @@ public HtmlElementTextContentFigure() } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementTextContentFigure(params IHtmlNode[] nodes) : this() { - Elements.AddRange(nodes); - } - - /// - /// Constructor - /// - /// The content of the html element. - public HtmlElementTextContentFigure(IEnumerable nodes) - : this() - { - base.Elements.AddRange(nodes); + Add(nodes); } } } diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementTextContentHr.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementTextContentHr.cs index 1c65c2f..9c1ef18 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementTextContentHr.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementTextContentHr.cs @@ -8,7 +8,7 @@ namespace WebExpress.WebCore.WebHtml public class HtmlElementTextContentHr : HtmlElement, IHtmlElementTextContent { /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementTextContentHr() : base("hr", false) diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementTextContentLi.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementTextContentLi.cs index 5398e4f..66e4668 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementTextContentLi.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementTextContentLi.cs @@ -10,10 +10,10 @@ public class HtmlElementTextContentLi : HtmlElement, IHtmlElementTextContent /// /// Returns the elements. /// - public new List Elements => base.Elements; + public new IEnumerable Elements => base.Elements; /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementTextContentLi() : base("li") @@ -21,23 +21,13 @@ public HtmlElementTextContentLi() } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementTextContentLi(params IHtmlNode[] nodes) : this() { - Elements.AddRange(nodes); - } - - /// - /// Constructor - /// - /// The content of the html element. - public HtmlElementTextContentLi(IEnumerable nodes) - : this() - { - Elements.AddRange(nodes); + Add(nodes); } } } diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementTextContentOl.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementTextContentOl.cs index 3a74f31..bc0106e 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementTextContentOl.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementTextContentOl.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq; using System.Text; namespace WebExpress.WebCore.WebHtml @@ -12,25 +13,26 @@ public class HtmlElementTextContentOl : HtmlElement, IHtmlElementTextContent /// /// Returns the elements. /// - public new List Elements { get; set; } + public new IEnumerable Elements => base.Elements + .Where(x => x is HtmlElementTextContentLi) + .Select(x => x as HtmlElementTextContentLi); /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementTextContentOl() : base("ol") { - Elements = new List(); } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementTextContentOl(params HtmlElementTextContentLi[] nodes) : this() { - Elements.AddRange(nodes); + Add(nodes); } /// @@ -40,9 +42,6 @@ public HtmlElementTextContentOl(params HtmlElementTextContentLi[] nodes) /// The call depth. public override void ToString(StringBuilder builder, int deep) { - base.Elements.Clear(); - base.Elements.AddRange(Elements); - base.ToString(builder, deep); } } diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementTextContentP.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementTextContentP.cs index fb0ecac..2f55435 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementTextContentP.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementTextContentP.cs @@ -14,11 +14,11 @@ public class HtmlElementTextContentP : HtmlElement, IHtmlElementTextContent public string Text { get => string.Join("", Elements.Where(x => x is HtmlText).Select(x => (x as HtmlText).Value)); - set { Elements.Clear(); Elements.Add(new HtmlText(value)); } + set { Clear(); Add(new HtmlText(value)); } } /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementTextContentP() : base("p") @@ -27,7 +27,7 @@ public HtmlElementTextContentP() } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementTextContentP(string text) @@ -37,13 +37,13 @@ public HtmlElementTextContentP(string text) } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementTextContentP(params IHtmlNode[] nodes) : this() { - Elements.AddRange(nodes); + Add(nodes); } /// diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementTextContentPre.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementTextContentPre.cs index 4fdfb6c..8a2cf35 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementTextContentPre.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementTextContentPre.cs @@ -10,10 +10,10 @@ public class HtmlElementTextContentPre : HtmlElement, IHtmlElementTextContent /// /// Returns the elements. /// - public new List Elements => base.Elements; + public new IEnumerable Elements => base.Elements; /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementTextContentPre() : base("pre") @@ -21,23 +21,13 @@ public HtmlElementTextContentPre() } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementTextContentPre(params IHtmlNode[] nodes) : this() { - Elements.AddRange(nodes); - } - - /// - /// Constructor - /// - /// The content of the html element. - public HtmlElementTextContentPre(IEnumerable nodes) - : this() - { - base.Elements.AddRange(nodes); + Add(nodes); } } } diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementTextContentUl.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementTextContentUl.cs index 5eb1978..9be3229 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementTextContentUl.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementTextContentUl.cs @@ -11,10 +11,10 @@ public class HtmlElementTextContentUl : HtmlElement, IHtmlElementTextContent /// /// Returns the elements. /// - public new List Elements => base.Elements; + public new IEnumerable Elements => base.Elements; /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementTextContentUl() : base("ul") @@ -22,23 +22,13 @@ public HtmlElementTextContentUl() } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementTextContentUl(params IHtmlNode[] nodes) : this() { - Elements.AddRange(nodes); - } - - /// - /// Constructor - /// - /// The content of the html element. - public HtmlElementTextContentUl(IEnumerable nodes) - : this() - { - base.Elements.AddRange(nodes); + Add(nodes); } } } diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsA.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsA.cs index 76a041d..025bb3e 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsA.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsA.cs @@ -12,7 +12,7 @@ public class HtmlElementTextSemanticsA : HtmlElement, IHtmlElementTextSemantics /// /// Returns the elements. /// - public new List Elements => base.Elements; + public new IEnumerable Elements => base.Elements; /// /// Returns or sets the text. @@ -20,7 +20,7 @@ public class HtmlElementTextSemanticsA : HtmlElement, IHtmlElementTextSemantics public string Text { get => string.Join("", Elements.Where(x => x is HtmlText).Select(x => (x as HtmlText).Value)); - set { Elements.Clear(); Elements.Add(new HtmlText(value)); } + set { Clear(); Add(new HtmlText(value)); } } /// @@ -60,7 +60,7 @@ public TypeTarget Target } /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementTextSemanticsA() : base("a") @@ -69,7 +69,7 @@ public HtmlElementTextSemanticsA() } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementTextSemanticsA(string text) @@ -79,23 +79,13 @@ public HtmlElementTextSemanticsA(string text) } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementTextSemanticsA(params IHtmlNode[] nodes) : this() { - Elements.AddRange(nodes); - } - - /// - /// Constructor - /// - /// The content of the html element. - public HtmlElementTextSemanticsA(IEnumerable nodes) - : this() - { - Elements.AddRange(nodes); + Add(nodes); } } } diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsAbbr.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsAbbr.cs index 17c06c4..132798f 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsAbbr.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsAbbr.cs @@ -10,10 +10,10 @@ public class HtmlElementTextSemanticsAbbr : HtmlElement, IHtmlElementTextSemanti /// /// Returns the elements. /// - public new List Elements => base.Elements; + public new IEnumerable Elements => base.Elements; /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementTextSemanticsAbbr() : base("abbr") @@ -21,23 +21,13 @@ public HtmlElementTextSemanticsAbbr() } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementTextSemanticsAbbr(params IHtmlNode[] nodes) : this() { - Elements.AddRange(nodes); - } - - /// - /// Constructor - /// - /// The content of the html element. - public HtmlElementTextSemanticsAbbr(IEnumerable nodes) - : this() - { - base.Elements.AddRange(nodes); + Add(nodes); } } } diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsB.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsB.cs index d006851..b25f1d7 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsB.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsB.cs @@ -15,11 +15,11 @@ public class HtmlElementTextSemanticsB : HtmlElement, IHtmlElementTextSemantics public string Text { get => string.Join("", Elements.Where(x => x is HtmlText).Select(x => (x as HtmlText).Value)); - set { Elements.Clear(); Elements.Add(new HtmlText(value)); } + set { Clear(); Add(new HtmlText(value)); } } /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementTextSemanticsB() : base("b") @@ -28,7 +28,7 @@ public HtmlElementTextSemanticsB() } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementTextSemanticsB(string text) @@ -38,13 +38,13 @@ public HtmlElementTextSemanticsB(string text) } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementTextSemanticsB(params IHtmlNode[] nodes) : this() { - Elements.AddRange(nodes); + Add(nodes); } /// diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsBdi.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsBdi.cs index 1a39e76..124f8b6 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsBdi.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsBdi.cs @@ -15,7 +15,7 @@ public class HtmlElementTextSemanticsBdi : HtmlElement, IHtmlElementTextSemantic public string Text { get => string.Join("", Elements.Where(x => x is HtmlText).Select(x => (x as HtmlText).Value)); - set { Elements.Clear(); Elements.Add(new HtmlText(value)); } + set { Clear(); Add(new HtmlText(value)); } } /// @@ -28,7 +28,7 @@ public string Dir } /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementTextSemanticsBdi() : base("bdi") @@ -37,7 +37,7 @@ public HtmlElementTextSemanticsBdi() } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementTextSemanticsBdi(string text) @@ -47,13 +47,13 @@ public HtmlElementTextSemanticsBdi(string text) } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementTextSemanticsBdi(params IHtmlNode[] nodes) : this() { - Elements.AddRange(nodes); + Add(nodes); } /// diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsBdo.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsBdo.cs index f993a57..001135e 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsBdo.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsBdo.cs @@ -14,7 +14,7 @@ public class HtmlElementTextSemanticsBdo : HtmlElement, IHtmlElementTextSemantic public string Text { get => string.Join("", Elements.Where(x => x is HtmlText).Select(x => (x as HtmlText).Value)); - set { Elements.Clear(); Elements.Add(new HtmlText(value)); } + set { Clear(); Add(new HtmlText(value)); } } /// @@ -27,7 +27,7 @@ public string Dir } /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementTextSemanticsBdo() : base("bdo") @@ -36,7 +36,7 @@ public HtmlElementTextSemanticsBdo() } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementTextSemanticsBdo(string text) @@ -46,13 +46,13 @@ public HtmlElementTextSemanticsBdo(string text) } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementTextSemanticsBdo(params IHtmlNode[] nodes) : this() { - Elements.AddRange(nodes); + Add(nodes); } /// diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsBr.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsBr.cs index 9d468b1..8817b2e 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsBr.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsBr.cs @@ -6,7 +6,7 @@ public class HtmlElementTextSemanticsBr : HtmlElement, IHtmlElementTextSemantics { /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementTextSemanticsBr() : base("br", false) diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsCite.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsCite.cs index f558ffc..e81cb14 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsCite.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsCite.cs @@ -14,16 +14,16 @@ public class HtmlElementTextSemanticsCite : HtmlElement, IHtmlElementTextSemanti public string Text { get => string.Join("", Elements.Where(x => x is HtmlText).Select(x => (x as HtmlText).Value)); - set { Elements.Clear(); Elements.Add(new HtmlText(value)); } + set { Clear(); Add(new HtmlText(value)); } } /// /// Returns the elements. /// - public new List Elements => base.Elements; + public new IEnumerable Elements => base.Elements; /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementTextSemanticsCite() : base("cite") @@ -32,7 +32,7 @@ public HtmlElementTextSemanticsCite() } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementTextSemanticsCite(string text) @@ -42,23 +42,13 @@ public HtmlElementTextSemanticsCite(string text) } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementTextSemanticsCite(params IHtmlNode[] nodes) : this() { - Elements.AddRange(nodes); - } - - /// - /// Constructor - /// - /// The content of the html element. - public HtmlElementTextSemanticsCite(IEnumerable nodes) - : this() - { - base.Elements.AddRange(nodes); + Add(nodes); } } } diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsCode.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsCode.cs index 5907db0..fdc6949 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsCode.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsCode.cs @@ -10,10 +10,10 @@ public class HtmlElementTextSemanticsCode : HtmlElement, IHtmlElementTextSemanti /// /// Returns the elements. /// - public new List Elements => base.Elements; + public new IEnumerable Elements => base.Elements; /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementTextSemanticsCode() : base("code") @@ -22,23 +22,13 @@ public HtmlElementTextSemanticsCode() } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementTextSemanticsCode(params IHtmlNode[] nodes) : this() { - Elements.AddRange(nodes); - } - - /// - /// Constructor - /// - /// The content of the html element. - public HtmlElementTextSemanticsCode(IEnumerable nodes) - : this() - { - base.Elements.AddRange(nodes); + Add(nodes); } } } diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsData.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsData.cs index 7f8e329..323de06 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsData.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsData.cs @@ -12,7 +12,7 @@ public class HtmlElementTextSemanticsData : HtmlElement, IHtmlElementTextSemanti /// /// Returns the elements. /// - public new List Elements => base.Elements; + public new IEnumerable Elements => base.Elements; /// /// Returns or sets the value. @@ -20,11 +20,11 @@ public class HtmlElementTextSemanticsData : HtmlElement, IHtmlElementTextSemanti public string Value { get => string.Join("", Elements.Where(x => x is HtmlText).Select(x => (x as HtmlText).Value)); - set { Elements.Clear(); Elements.Add(new HtmlText(value)); } + set { Clear(); Add(new HtmlText(value)); } } /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementTextSemanticsData() : base("data") @@ -32,23 +32,13 @@ public HtmlElementTextSemanticsData() } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementTextSemanticsData(params IHtmlNode[] nodes) : this() { - Elements.AddRange(nodes); - } - - /// - /// Constructor - /// - /// The content of the html element. - public HtmlElementTextSemanticsData(IEnumerable nodes) - : this() - { - base.Elements.AddRange(nodes); + Add(nodes); } } } diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsDfn.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsDfn.cs index 41a3394..23bdc81 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsDfn.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsDfn.cs @@ -10,10 +10,10 @@ public class HtmlElementTextSemanticsDfn : HtmlElement, IHtmlElementTextSemantic /// /// Returns the elements. /// - public new List Elements => base.Elements; + public new IEnumerable Elements => base.Elements; /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementTextSemanticsDfn() : base("dfn") @@ -21,23 +21,13 @@ public HtmlElementTextSemanticsDfn() } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementTextSemanticsDfn(params IHtmlNode[] nodes) : this() { - Elements.AddRange(nodes); - } - - /// - /// Constructor - /// - /// The content of the html element. - public HtmlElementTextSemanticsDfn(IEnumerable nodes) - : this() - { - base.Elements.AddRange(nodes); + Add(nodes); } } } diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsEm.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsEm.cs index 274a6a9..ee65423 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsEm.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsEm.cs @@ -10,10 +10,10 @@ public class HtmlElementTextSemanticsEm : HtmlElement, IHtmlElementTextSemantics /// /// Returns the elements. /// - public new List Elements => base.Elements; + public new IEnumerable Elements => base.Elements; /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementTextSemanticsEm() : base("em") @@ -22,23 +22,13 @@ public HtmlElementTextSemanticsEm() } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementTextSemanticsEm(params IHtmlNode[] nodes) : this() { - Elements.AddRange(nodes); - } - - /// - /// Constructor - /// - /// The content of the html element. - public HtmlElementTextSemanticsEm(IEnumerable nodes) - : this() - { - base.Elements.AddRange(nodes); + Add(nodes); } } } diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsI.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsI.cs index 465265b..54360c4 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsI.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsI.cs @@ -17,11 +17,11 @@ public class HtmlElementTextSemanticsI : HtmlElement, IHtmlElementTextSemantics public string Text { get => string.Join("", Elements.Where(x => x is HtmlText).Select(x => (x as HtmlText).Value)); - set { Elements.Clear(); Elements.Add(new HtmlText(value)); } + set { Clear(); Add(new HtmlText(value)); } } /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementTextSemanticsI() : base("i") @@ -29,7 +29,7 @@ public HtmlElementTextSemanticsI() } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementTextSemanticsI(string text) @@ -39,13 +39,13 @@ public HtmlElementTextSemanticsI(string text) } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementTextSemanticsI(params IHtmlNode[] nodes) : this() { - Elements.AddRange(nodes); + Add(nodes); } /// diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsKdb.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsKdb.cs index de93eec..4b56b20 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsKdb.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsKdb.cs @@ -11,10 +11,10 @@ public class HtmlElementTextSemanticsKdb : HtmlElement, IHtmlElementTextSemantic /// /// Returns the elements. /// - public new List Elements => base.Elements; + public new IEnumerable Elements => base.Elements; /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementTextSemanticsKdb() : base("kdb") @@ -22,23 +22,13 @@ public HtmlElementTextSemanticsKdb() } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementTextSemanticsKdb(params IHtmlNode[] nodes) : this() { - Elements.AddRange(nodes); - } - - /// - /// Constructor - /// - /// The content of the html element. - public HtmlElementTextSemanticsKdb(IEnumerable nodes) - : this() - { - base.Elements.AddRange(nodes); + Add(nodes); } } } diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsMark.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsMark.cs index 0cce976..f89cab1 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsMark.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsMark.cs @@ -14,11 +14,11 @@ public class HtmlElementTextSemanticsMark : HtmlElement, IHtmlElementTextSemanti public string Text { get => string.Join("", Elements.Where(x => x is HtmlText).Select(x => (x as HtmlText).Value)); - set { Elements.Clear(); Elements.Add(new HtmlText(value)); } + set { Clear(); Add(new HtmlText(value)); } } /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementTextSemanticsMark() : base("mark") @@ -26,7 +26,7 @@ public HtmlElementTextSemanticsMark() } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementTextSemanticsMark(string text) @@ -36,13 +36,13 @@ public HtmlElementTextSemanticsMark(string text) } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementTextSemanticsMark(params IHtmlNode[] nodes) : this() { - Elements.AddRange(nodes); + Add(nodes); } /// diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsQ.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsQ.cs index 18ef751..16d580a 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsQ.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsQ.cs @@ -3,17 +3,17 @@ namespace WebExpress.WebCore.WebHtml { /// - /// Represents a short quote. For longer quotes should
be used. + /// Represents a short quote. For longer quotes should blockquote be used. ///
public class HtmlElementTextSemanticsQ : HtmlElement, IHtmlElementTextSemantics { /// /// Returns the elements. /// - public new List Elements => base.Elements; + public new IEnumerable Elements => base.Elements; /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementTextSemanticsQ() : base("q") @@ -21,23 +21,13 @@ public HtmlElementTextSemanticsQ() } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementTextSemanticsQ(params IHtmlNode[] nodes) : this() { - Elements.AddRange(nodes); - } - - /// - /// Constructor - /// - /// The content of the html element. - public HtmlElementTextSemanticsQ(IEnumerable nodes) - : this() - { - base.Elements.AddRange(nodes); + Add(nodes); } } } diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsRp.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsRp.cs index e9d4b46..78cc839 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsRp.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsRp.cs @@ -4,7 +4,7 @@ namespace WebExpress.WebCore.WebHtml { /// - /// Used along with the element to surround Ruby text with parentheses that + /// Used along with the element ruby to surround Ruby text with parentheses that /// appear when the user program (browser) does not support Ruby annotations. /// public class HtmlElementTextSemanticsRp : HtmlElement, IHtmlElementTextSemantics @@ -15,11 +15,11 @@ public class HtmlElementTextSemanticsRp : HtmlElement, IHtmlElementTextSemantics public string Text { get => string.Join("", Elements.Where(x => x is HtmlText).Select(x => (x as HtmlText).Value)); - set { Elements.Clear(); Elements.Add(new HtmlText(value)); } + set { Clear(); Add(new HtmlText(value)); } } /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementTextSemanticsRp() : base("rp") @@ -27,7 +27,7 @@ public HtmlElementTextSemanticsRp() } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementTextSemanticsRp(string text) @@ -37,13 +37,13 @@ public HtmlElementTextSemanticsRp(string text) } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementTextSemanticsRp(params IHtmlNode[] nodes) : this() { - Elements.AddRange(nodes); + Add(nodes); } /// diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsRt.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsRt.cs index c062aba..441f9c7 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsRt.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsRt.cs @@ -14,11 +14,11 @@ public class HtmlElementTextSemanticsRt : HtmlElement, IHtmlElementTextSemantics public string Text { get => string.Join("", Elements.Where(x => x is HtmlText).Select(x => (x as HtmlText).Value)); - set { Elements.Clear(); Elements.Add(new HtmlText(value)); } + set { Clear(); Add(new HtmlText(value)); } } /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementTextSemanticsRt() : base("rt") @@ -26,7 +26,7 @@ public HtmlElementTextSemanticsRt() } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementTextSemanticsRt(string text) @@ -36,13 +36,13 @@ public HtmlElementTextSemanticsRt(string text) } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementTextSemanticsRt(params IHtmlNode[] nodes) : this() { - Elements.AddRange(nodes); + Add(nodes); } /// diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsRuby.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsRuby.cs index f1fec38..fb031f7 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsRuby.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsRuby.cs @@ -11,10 +11,10 @@ public class HtmlElementTextSemanticsRuby : HtmlElement, IHtmlElementTextSemanti /// /// Returns the elements. /// - public new List Elements => base.Elements; + public new IEnumerable Elements => base.Elements; /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementTextSemanticsRuby() : base("ruby") @@ -22,23 +22,13 @@ public HtmlElementTextSemanticsRuby() } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementTextSemanticsRuby(params IHtmlNode[] nodes) : this() { - Elements.AddRange(nodes); - } - - /// - /// Constructor - /// - /// The content of the html element. - public HtmlElementTextSemanticsRuby(IEnumerable nodes) - : this() - { - base.Elements.AddRange(nodes); + Add(nodes); } } } diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsS.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsS.cs index b6733e4..a148deb 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsS.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsS.cs @@ -14,11 +14,11 @@ public class HtmlElementTextSemanticsS : HtmlElement, IHtmlElementTextSemantics public string Text { get => string.Join("", Elements.Where(x => x is HtmlText).Select(x => (x as HtmlText).Value)); - set { Elements.Clear(); Elements.Add(new HtmlText(value)); } + set { Clear(); Add(new HtmlText(value)); } } /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementTextSemanticsS() : base("s") @@ -26,7 +26,7 @@ public HtmlElementTextSemanticsS() } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementTextSemanticsS(string text) @@ -36,13 +36,13 @@ public HtmlElementTextSemanticsS(string text) } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementTextSemanticsS(params IHtmlNode[] nodes) : this() { - Elements.AddRange(nodes); + Add(nodes); } /// diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsSamp.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsSamp.cs index 2460006..2e2083a 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsSamp.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsSamp.cs @@ -10,10 +10,10 @@ public class HtmlElementTextSemanticsSamp : HtmlElement, IHtmlElementTextSemanti /// /// Returns the elements. /// - public new List Elements => base.Elements; + public new IEnumerable Elements => base.Elements; /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementTextSemanticsSamp() : base("samp") @@ -22,23 +22,13 @@ public HtmlElementTextSemanticsSamp() } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementTextSemanticsSamp(params IHtmlNode[] nodes) : this() { - Elements.AddRange(nodes); - } - - /// - /// Constructor - /// - /// The content of the html element. - public HtmlElementTextSemanticsSamp(IEnumerable nodes) - : this() - { - base.Elements.AddRange(nodes); + Add(nodes); } } } diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsSmall.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsSmall.cs index 798faab..0ce07ca 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsSmall.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsSmall.cs @@ -15,11 +15,11 @@ public class HtmlElementTextSemanticsSmall : HtmlElement, IHtmlElementTextSemant public string Text { get => string.Join("", Elements.Where(x => x is HtmlText).Select(x => (x as HtmlText).Value)); - set { Elements.Clear(); Elements.Add(new HtmlText(value)); } + set { Clear(); Add(new HtmlText(value)); } } /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementTextSemanticsSmall() : base("small") @@ -28,7 +28,7 @@ public HtmlElementTextSemanticsSmall() } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementTextSemanticsSmall(string text) @@ -38,13 +38,13 @@ public HtmlElementTextSemanticsSmall(string text) } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementTextSemanticsSmall(params IHtmlNode[] nodes) : this() { - Elements.AddRange(nodes); + Add(nodes); } /// diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsSpan.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsSpan.cs index 38223fa..f265921 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsSpan.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsSpan.cs @@ -10,10 +10,10 @@ public class HtmlElementTextSemanticsSpan : HtmlElement, IHtmlElementTextSemanti /// /// Returns the elements. /// - public new List Elements => base.Elements; + public new IEnumerable Elements => base.Elements; /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementTextSemanticsSpan() : base("span") @@ -21,23 +21,13 @@ public HtmlElementTextSemanticsSpan() } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementTextSemanticsSpan(params IHtmlNode[] nodes) : this() { - Elements.AddRange(nodes); - } - - /// - /// Constructor - /// - /// The content of the html element. - public HtmlElementTextSemanticsSpan(IEnumerable nodes) - : this() - { - base.Elements.AddRange(nodes); + Add(nodes); } } } diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsStrong.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsStrong.cs index 712f0af..04fc5c6 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsStrong.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsStrong.cs @@ -14,11 +14,11 @@ public class HtmlElementTextSemanticsStrong : HtmlElement, IHtmlElementTextSeman public string Text { get => string.Join("", Elements.Where(x => x is HtmlText).Select(x => (x as HtmlText).Value)); - set { Elements.Clear(); Elements.Add(new HtmlText(value)); } + set { Clear(); Add(new HtmlText(value)); } } /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementTextSemanticsStrong() : base("strong") @@ -26,7 +26,7 @@ public HtmlElementTextSemanticsStrong() } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementTextSemanticsStrong(string text) @@ -36,13 +36,13 @@ public HtmlElementTextSemanticsStrong(string text) } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementTextSemanticsStrong(params IHtmlNode[] nodes) : this() { - Elements.AddRange(nodes); + Add(nodes); } /// diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsSub.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsSub.cs index 0f3f39a..c930ebb 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsSub.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsSub.cs @@ -10,10 +10,10 @@ public class HtmlElementTextSemanticsSub : HtmlElement, IHtmlElementTextSemantic /// /// Returns the elements. /// - public new List Elements => base.Elements; + public new IEnumerable Elements => base.Elements; /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementTextSemanticsSub() : base("sub") @@ -21,23 +21,13 @@ public HtmlElementTextSemanticsSub() } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementTextSemanticsSub(params IHtmlNode[] nodes) : this() { - Elements.AddRange(nodes); - } - - /// - /// Constructor - /// - /// The content of the html element. - public HtmlElementTextSemanticsSub(IEnumerable nodes) - : this() - { - base.Elements.AddRange(nodes); + Add(nodes); } } } diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsSup.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsSup.cs index 214cf00..adce2e4 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsSup.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsSup.cs @@ -10,10 +10,10 @@ public class HtmlElementTextSemanticsSup : HtmlElement, IHtmlElementTextSemantic /// /// Returns the elements. /// - public new List Elements => base.Elements; + public new IEnumerable Elements => base.Elements; /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementTextSemanticsSup() : base("sup") @@ -21,23 +21,13 @@ public HtmlElementTextSemanticsSup() } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementTextSemanticsSup(params IHtmlNode[] nodes) : this() { - Elements.AddRange(nodes); - } - - /// - /// Constructor - /// - /// The content of the html element. - public HtmlElementTextSemanticsSup(IEnumerable nodes) - : this() - { - base.Elements.AddRange(nodes); + Add(nodes); } } } diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsTime.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsTime.cs index 3796318..5756c1d 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsTime.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsTime.cs @@ -14,11 +14,11 @@ public class HtmlElementTextSemanticsTime : HtmlElement, IHtmlElementTextSemanti public string Time { get => string.Join("", Elements.Where(x => x is HtmlText).Select(x => (x as HtmlText).Value)); - set { Elements.Clear(); Elements.Add(new HtmlText(value)); } + set { Clear(); Add(new HtmlText(value)); } } /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementTextSemanticsTime() : base("time") @@ -26,13 +26,13 @@ public HtmlElementTextSemanticsTime() } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementTextSemanticsTime(params IHtmlNode[] nodes) : this() { - Elements.AddRange(nodes); + Add(nodes); } /// diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsU.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsU.cs index b83f890..9905710 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsU.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsU.cs @@ -16,11 +16,11 @@ public class HtmlElementTextSemanticsU : HtmlElement, IHtmlElementTextSemantics public string Text { get => string.Join("", Elements.Where(x => x is HtmlText).Select(x => (x as HtmlText).Value)); - set { Elements.Clear(); Elements.Add(new HtmlText(value)); } + set { Clear(); Add(new HtmlText(value)); } } /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementTextSemanticsU() : base("u") @@ -28,7 +28,7 @@ public HtmlElementTextSemanticsU() } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementTextSemanticsU(string text) @@ -38,13 +38,13 @@ public HtmlElementTextSemanticsU(string text) } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementTextSemanticsU(params IHtmlNode[] nodes) : this() { - Elements.AddRange(nodes); + Add(nodes); } /// diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsVar.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsVar.cs index 0f42451..8ae59b1 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsVar.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsVar.cs @@ -12,10 +12,10 @@ public class HtmlElementTextSemanticsVar : HtmlElement, IHtmlElementTextSemantic /// /// Returns the elements. /// - public new List Elements => base.Elements; + public new IEnumerable Elements => base.Elements; /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementTextSemanticsVar() : base("var") @@ -23,23 +23,13 @@ public HtmlElementTextSemanticsVar() } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementTextSemanticsVar(params IHtmlNode[] nodes) : this() { - Elements.AddRange(nodes); - } - - /// - /// Constructor - /// - /// The content of the html element. - public HtmlElementTextSemanticsVar(IEnumerable nodes) - : this() - { - base.Elements.AddRange(nodes); + Add(nodes); } } } diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsWbr.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsWbr.cs index fd8303d..5d55ebe 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsWbr.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementTextSemanticsWbr.cs @@ -7,7 +7,7 @@ public class HtmlElementTextSemanticsWbr : HtmlElement, IHtmlElementTextSemantics { /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementTextSemanticsWbr() : base("wbr", false) diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementWebComponentsSlot.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementWebComponentsSlot.cs index b4d4fc7..64aef21 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementWebComponentsSlot.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementWebComponentsSlot.cs @@ -10,10 +10,10 @@ public class HtmlElementWebFragmentsSlot : HtmlElement, IHtmlElementWebFragments /// /// Returns the elements. /// - public new List Elements => base.Elements; + public new IEnumerable Elements => base.Elements; /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementWebFragmentsSlot() : base("slot") @@ -21,23 +21,13 @@ public HtmlElementWebFragmentsSlot() } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementWebFragmentsSlot(params IHtmlNode[] nodes) : this() { - Elements.AddRange(nodes); - } - - /// - /// Constructor - /// - /// The content of the html element. - public HtmlElementWebFragmentsSlot(IEnumerable nodes) - : this() - { - base.Elements.AddRange(nodes); + Add(nodes); } } } diff --git a/src/WebExpress.WebCore/WebHtml/HtmlElementWebComponentsTemplate.cs b/src/WebExpress.WebCore/WebHtml/HtmlElementWebComponentsTemplate.cs index 77002bb..ac7741c 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlElementWebComponentsTemplate.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlElementWebComponentsTemplate.cs @@ -10,10 +10,10 @@ public class HtmlElementWebFragmentsTemplate : HtmlElement, IHtmlElementWebFragm /// /// Returns the elements. /// - public new List Elements => base.Elements; + public new IEnumerable Elements => base.Elements; /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlElementWebFragmentsTemplate() : base("template") @@ -21,23 +21,13 @@ public HtmlElementWebFragmentsTemplate() } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlElementWebFragmentsTemplate(params IHtmlNode[] nodes) : this() { - Elements.AddRange(nodes); - } - - /// - /// Constructor - /// - /// The content of the html element. - public HtmlElementWebFragmentsTemplate(IEnumerable nodes) - : this() - { - base.Elements.AddRange(nodes); + Add(nodes); } } } diff --git a/src/WebExpress.WebCore/WebHtml/HtmlEmpty.cs b/src/WebExpress.WebCore/WebHtml/HtmlEmpty.cs index 97f1424..c7c3a31 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlEmpty.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlEmpty.cs @@ -2,6 +2,9 @@ namespace WebExpress.WebCore.WebHtml { + /// + /// Represents an empty HTML node. + /// public class HtmlEmpty : IHtmlNode { /// @@ -10,7 +13,7 @@ public class HtmlEmpty : IHtmlNode public string Value { get; set; } /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlEmpty() { diff --git a/src/WebExpress.WebCore/WebHtml/HtmlList.cs b/src/WebExpress.WebCore/WebHtml/HtmlList.cs index a59d567..29144a1 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlList.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlList.cs @@ -8,61 +8,71 @@ namespace WebExpress.WebCore.WebHtml /// public class HtmlList : IHtmlNode { + private readonly List _elements = []; + /// /// Returns the elements. /// - public List Elements { get; private set; } + public IEnumerable Elements => _elements; /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlList() { - Elements = new List(); } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlList(params IHtmlNode[] nodes) : this() { - Elements.AddRange(nodes); + _elements.AddRange(nodes); } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The first content of the html element. /// The following contents of the html elements. public HtmlList(IHtmlNode firstNode, params IHtmlNode[] followingNodes) : this() { - Elements.Add(firstNode); - Elements.AddRange(followingNodes); + _elements.Add(firstNode); + _elements.AddRange(followingNodes); } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The content of the html element. public HtmlList(IEnumerable nodes) : this() { - Elements.AddRange(nodes); + _elements.AddRange(nodes); } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The first content of the html element. /// The following contents of the html elements. public HtmlList(IHtmlNode firstNode, IEnumerable followingNodes) : this() { - Elements.Add(firstNode); - Elements.AddRange(followingNodes); + _elements.Add(firstNode); + _elements.AddRange(followingNodes); + } + + /// + /// Adds one or more elements to the list. + /// + /// The elements to add. + protected void Add(params IHtmlNode[] elements) + { + _elements.AddRange(elements); } /// @@ -70,13 +80,25 @@ public HtmlList(IHtmlNode firstNode, IEnumerable followingNodes) /// /// The string builder. /// The call depth. - /// Start the closing tag on a new line. public void ToString(StringBuilder builder, int deep) { - foreach (var v in Elements) + foreach (var v in _elements) { v.ToString(builder, deep); } } + + /// + /// Converts the HTML list to its string representation. + /// + /// A string that represents the HTML list. + public override string ToString() + { + var builder = new StringBuilder(); + + ToString(builder, 0); + + return builder.ToString(); + } } } diff --git a/src/WebExpress.WebCore/WebHtml/HtmlNbsp.cs b/src/WebExpress.WebCore/WebHtml/HtmlNbsp.cs index 7963300..7d77912 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlNbsp.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlNbsp.cs @@ -13,7 +13,7 @@ public class HtmlNbsp : IHtmlNode public string Value { get; set; } /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlNbsp() { diff --git a/src/WebExpress.WebCore/WebHtml/HtmlRaw.cs b/src/WebExpress.WebCore/WebHtml/HtmlRaw.cs index f44c3df..184496a 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlRaw.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlRaw.cs @@ -2,6 +2,12 @@ namespace WebExpress.WebCore.WebHtml { + /// + /// Represents a raw HTML node. + /// + /// + /// This class is used to encapsulate raw HTML content. + /// public class HtmlRaw : IHtmlNode { /// @@ -10,15 +16,14 @@ public class HtmlRaw : IHtmlNode public string Html { get; set; } /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlRaw() { - } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The text. public HtmlRaw(string html) diff --git a/src/WebExpress.WebCore/WebHtml/HtmlText.cs b/src/WebExpress.WebCore/WebHtml/HtmlText.cs index a528f66..a40a845 100644 --- a/src/WebExpress.WebCore/WebHtml/HtmlText.cs +++ b/src/WebExpress.WebCore/WebHtml/HtmlText.cs @@ -13,14 +13,14 @@ public class HtmlText : IHtmlNode public string Value { get; set; } /// - /// Constructor + /// Initializes a new instance of the class. /// public HtmlText() { } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The text. public HtmlText(string value) diff --git a/src/WebExpress.WebCore/WebHtml/IHtml.cs b/src/WebExpress.WebCore/WebHtml/IHtml.cs index da9ce08..be25f43 100644 --- a/src/WebExpress.WebCore/WebHtml/IHtml.cs +++ b/src/WebExpress.WebCore/WebHtml/IHtml.cs @@ -2,6 +2,9 @@ namespace WebExpress.WebCore.WebHtml { + /// + /// Interface for HTML elements. + /// public interface IHtml { /// diff --git a/src/WebExpress.WebCore/WebHtml/IHtmlAttribute.cs b/src/WebExpress.WebCore/WebHtml/IHtmlAttribute.cs index 8a54ec3..4f8b2af 100644 --- a/src/WebExpress.WebCore/WebHtml/IHtmlAttribute.cs +++ b/src/WebExpress.WebCore/WebHtml/IHtmlAttribute.cs @@ -1,9 +1,12 @@ namespace WebExpress.WebCore.WebHtml { + /// + /// Represents an HTML attribute that can be added to an HTML element. + /// public interface IHtmlAttribute : IHtml { /// - /// Returns or sets the name. des Attributes + /// Returns or sets the attribute name. /// string Name { get; set; } diff --git a/src/WebExpress.WebCore/WebHtml/IHtmlElementFormItem.cs b/src/WebExpress.WebCore/WebHtml/IHtmlElementFormItem.cs new file mode 100644 index 0000000..755e5a9 --- /dev/null +++ b/src/WebExpress.WebCore/WebHtml/IHtmlElementFormItem.cs @@ -0,0 +1,9 @@ +namespace WebExpress.WebCore.WebHtml +{ + /// + /// Represents an element that is part of a form item. + /// + public interface IHtmlElementFormItem : IHtmlElementForm + { + } +} diff --git a/src/WebExpress.WebCore/WebHtml/IHtmlFormularItem.cs b/src/WebExpress.WebCore/WebHtml/IHtmlFormularItem.cs deleted file mode 100644 index 1de6b0a..0000000 --- a/src/WebExpress.WebCore/WebHtml/IHtmlFormularItem.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace WebExpress.WebCore.WebHtml -{ - public interface IHtmlFormularItem : IHtmlElementForm - { - } -} diff --git a/src/WebExpress.WebCore/WebHtml/Style.cs b/src/WebExpress.WebCore/WebHtml/Style.cs index 3ee465a..27d6e02 100644 --- a/src/WebExpress.WebCore/WebHtml/Style.cs +++ b/src/WebExpress.WebCore/WebHtml/Style.cs @@ -2,6 +2,9 @@ namespace WebExpress.WebCore.WebHtml { + /// + /// Represents utility methods for managing CSS styles. + /// public static class Style { /// diff --git a/src/WebExpress.WebCore/WebHtml/TypeEnctype.cs b/src/WebExpress.WebCore/WebHtml/TypeEnctype.cs index 18146b4..8fad32e 100644 --- a/src/WebExpress.WebCore/WebHtml/TypeEnctype.cs +++ b/src/WebExpress.WebCore/WebHtml/TypeEnctype.cs @@ -9,20 +9,26 @@ public enum TypeEnctype /// All characters are encoded (spaces are conferred to "+" and special characters in the hex representation). /// UrLEncoded, + /// /// No characters will be encodes. Used when transferring files. /// None, + /// /// Only space characters are encoded. /// Text, + /// /// Not assignable. /// Default } + /// + /// Provides extension methods for the TypeEnctype enumeration. + /// public static class TypeEnctypeExtensions { /// @@ -41,7 +47,6 @@ public static TypeEnctype Convert(string enctype) }; } - /// /// Conversion to string.repräsentation /// diff --git a/src/WebExpress.WebCore/WebHtml/TypeFavicon.cs b/src/WebExpress.WebCore/WebHtml/TypeFavicon.cs index 8a689c6..f0c855b 100644 --- a/src/WebExpress.WebCore/WebHtml/TypeFavicon.cs +++ b/src/WebExpress.WebCore/WebHtml/TypeFavicon.cs @@ -1,11 +1,33 @@ namespace WebExpress.WebCore.WebHtml { + /// + /// Enumeration representing the different types of favicons. + /// public enum TypeFavicon { + /// + /// The default favicon type. + /// Default, + + /// + /// Favicon type for .ico files. + /// ICON, + + /// + /// Favicon type for .png files. + /// PNG, + + /// + /// Favicon type for .jpg files. + /// JPG, + + /// + /// Favicon type for .svg files. + /// SVG } } diff --git a/src/WebExpress.WebCore/WebHtml/TypeTarget.cs b/src/WebExpress.WebCore/WebHtml/TypeTarget.cs index 25e5b67..ae56f13 100644 --- a/src/WebExpress.WebCore/WebHtml/TypeTarget.cs +++ b/src/WebExpress.WebCore/WebHtml/TypeTarget.cs @@ -1,15 +1,44 @@ namespace WebExpress.WebCore.WebHtml { + /// + /// Specifies the target for hyperlinks or forms. + /// public enum TypeTarget { + /// + /// No target specified. + /// None, + + /// + /// Opens the link in a new window or tab. + /// Blank, + + /// + /// Opens the link in the same frame as it was clicked. + /// Self, + + /// + /// Opens the link in the parent frame. + /// Parent, + + /// + /// Opens the link in the full body of the window. + /// Top, + + /// + /// Opens the link in a named frame. + /// Framename } + /// + /// Provides extension methods for the TypeTarget enum. + /// public static class TypeTargetExtensions { /// diff --git a/src/WebExpress.WebCore/WebIcon/IIcon.cs b/src/WebExpress.WebCore/WebIcon/IIcon.cs new file mode 100644 index 0000000..7dfd0c0 --- /dev/null +++ b/src/WebExpress.WebCore/WebIcon/IIcon.cs @@ -0,0 +1,24 @@ +using WebExpress.WebCore.WebHtml; +using WebExpress.WebCore.WebPage; + +namespace WebExpress.WebCore.WebIcon +{ + /// + /// Represents an icon that can be rendered to HTML. + /// + public interface IIcon + { + /// + /// Converts the icon to an HTML representation. + /// + /// The context in which the icon is rendered. + /// The visual tree representing the icon's structure. + /// The id attribute of the HTML element. + /// The description of the icon. + /// The CSS class of the HTML element. + /// The inline style of the HTML element. + /// The ARIA role of the HTML element. + /// An HTML node representing the rendered icon. + IHtmlNode Render(IRenderContext renderContext, IVisualTree visualTree, string id = null, string description = null, string css = null, string style = null, string role = null); + } +} diff --git a/src/WebExpress.WebCore/WebIdentity/IIdentity.cs b/src/WebExpress.WebCore/WebIdentity/IIdentity.cs new file mode 100644 index 0000000..547d7a5 --- /dev/null +++ b/src/WebExpress.WebCore/WebIdentity/IIdentity.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; + +namespace WebExpress.WebCore.WebIdentity +{ + /// + /// Represents an identity in the web application. + /// + public interface IIdentity + { + /// + /// Returns the id of the identity. + /// + public Guid Id { get; } + + /// + /// Returns the name of the identity. + /// + public string Name { get; } + + /// + /// Returns the email of the identity. + /// + public string Email { get; } + + /// + /// Returns the hash of the password. + /// + string PasswordHash { get; } + + /// + /// Returns the groups associated with the identity. + /// + IEnumerable Groups { get; } + } +} diff --git a/src/WebExpress.WebCore/WebIdentity/IIdentityGroup.cs b/src/WebExpress.WebCore/WebIdentity/IIdentityGroup.cs new file mode 100644 index 0000000..d8a98a8 --- /dev/null +++ b/src/WebExpress.WebCore/WebIdentity/IIdentityGroup.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; + +namespace WebExpress.WebCore.WebIdentity +{ + /// + /// Interface that defines an identity group. + /// + public interface IIdentityGroup + { + /// + /// Returns the roles associated with the group. + /// + IEnumerable Roles { get; } + } +} diff --git a/src/WebExpress.WebCore/WebIdentity/IIdentityManager.cs b/src/WebExpress.WebCore/WebIdentity/IIdentityManager.cs new file mode 100644 index 0000000..34c29be --- /dev/null +++ b/src/WebExpress.WebCore/WebIdentity/IIdentityManager.cs @@ -0,0 +1,115 @@ +using System; +using System.Collections.Generic; +using System.Security; +using WebExpress.WebCore.WebApplication; +using WebExpress.WebCore.WebComponent; +using WebExpress.WebCore.WebMessage; + +namespace WebExpress.WebCore.WebIdentity +{ + /// + /// Interface for managing identities. + /// + public interface IIdentityManager : IComponentManager + { + /// + /// Returns all permissions. + /// + public IEnumerable Permissions { get; } + + /// + /// Returns all roles. + /// + public IEnumerable Roles { get; } + + /// + /// Returns all identities. + /// + IEnumerable Identities { get; } + + /// + /// Returns the current signed-in identity. + /// + IIdentity CurrentIdentity { get; } + + /// + /// Login an identity. + /// + /// The request. + /// The identity. + /// The password. + /// True if successful, false otherwise. + bool Login(Request request, IIdentity identity, SecureString password); + + /// + /// Logout an identity. + /// + /// The request. + void Logout(Request request); + + /// + /// Returns the current signed-in identity based on the provided request. + /// + /// The request to get the current identity for. + /// The current signed-in identity. + IIdentity GetCurrentIdentity(Request request); + + /// + /// Checks if the specified identity has the given permission. + /// + /// The type of the identity permission. + /// The context of the application. + /// The identity to check. + /// True if the identity has the permission, false otherwise. + bool CheckAccess(IApplicationContext applicationContext, IIdentity identity) + where TIdentityPermission : IIdentityPermission; + + /// + /// Checks if the specified identity has the given permission. + /// + /// The context of the application. + /// The identity to check. + /// The permission to check for. + /// True if the identity has the permission, false otherwise. + bool CheckAccess(IApplicationContext applicationContext, IIdentity identity, Type permission); + + /// + /// Checks if the specified identity group has the given permission. + /// + /// The type of the identity permission. + /// The context of the application. + /// The identity group to check. + /// True if the identity group has the permission, false otherwise. + bool CheckAccess(IApplicationContext applicationContext, IIdentityGroup group) + where TIdentityPermission : IIdentityPermission; + + /// + /// Checks if the specified identity group has the given permission. + /// + /// The context of the application. + /// The identity group to check. + /// The permission to check for. + /// True if the identity group has the permission, false otherwise. + bool CheckAccess(IApplicationContext applicationContext, IIdentityGroup group, Type permission); + + /// + /// Checks if the specified identity role has the given permission. + /// + /// The type of the identity role. + /// The type of the identity permission. + /// The context of the application. + /// True if the identity role has the permission, false otherwise. + bool CheckAccess(IApplicationContext applicationContext) + where TIdentityRole : IIdentityRole + where TIdentityPermission : IIdentityPermission; + + /// + /// Checks if the specified identity role has the given permission. + /// + /// The context of the application. + /// The identity role to check. + /// The permission to check for. + /// True if the identity role has the permission, false otherwise. + bool CheckAccess(IApplicationContext applicationContext, Type role, Type permission); + } +} diff --git a/src/WebExpress.WebCore/WebIdentity/IIdentityPermission.cs b/src/WebExpress.WebCore/WebIdentity/IIdentityPermission.cs new file mode 100644 index 0000000..67fe2c6 --- /dev/null +++ b/src/WebExpress.WebCore/WebIdentity/IIdentityPermission.cs @@ -0,0 +1,12 @@ + +using WebExpress.WebCore.WebComponent; + +namespace WebExpress.WebCore.WebIdentity +{ + /// + /// Interface that defines an identity permission. + /// + public interface IIdentityPermission : IComponent + { + } +} diff --git a/src/WebExpress.WebCore/WebIdentity/IIdentityPermissionContext.cs b/src/WebExpress.WebCore/WebIdentity/IIdentityPermissionContext.cs new file mode 100644 index 0000000..870b8a1 --- /dev/null +++ b/src/WebExpress.WebCore/WebIdentity/IIdentityPermissionContext.cs @@ -0,0 +1,33 @@ +using System; +using WebExpress.WebCore.WebApplication; +using WebExpress.WebCore.WebComponent; +using WebExpress.WebCore.WebPlugin; + +namespace WebExpress.WebCore.WebIdentity +{ + /// + /// Defines the context for a permission, providing access to various related contexts and properties. + /// + public interface IIdentityPermissionContext : IContext + { + /// + /// Returns the permission id. + /// + IComponentId PermissionId { get; } + + /// + /// Returns the permission. + /// + Type Permission { get; } + + /// + /// Returns the associated plugin context. + /// + IPluginContext PluginContext { get; } + + /// + /// Returns the corresponding application context. + /// + IApplicationContext ApplicationContext { get; } + } +} diff --git a/src/WebExpress.WebCore/WebIdentity/IIdentityResource.cs b/src/WebExpress.WebCore/WebIdentity/IIdentityResource.cs new file mode 100644 index 0000000..ce18045 --- /dev/null +++ b/src/WebExpress.WebCore/WebIdentity/IIdentityResource.cs @@ -0,0 +1,9 @@ +namespace WebExpress.WebCore.WebIdentity +{ + /// + /// Interface for identity resources. + /// + public interface IIdentityResource + { + } +} diff --git a/src/WebExpress.WebCore/WebIdentity/IIdentityRole.cs b/src/WebExpress.WebCore/WebIdentity/IIdentityRole.cs new file mode 100644 index 0000000..b48dc12 --- /dev/null +++ b/src/WebExpress.WebCore/WebIdentity/IIdentityRole.cs @@ -0,0 +1,11 @@ +using WebExpress.WebCore.WebComponent; + +namespace WebExpress.WebCore.WebIdentity +{ + /// + /// Interface that defines an identity role. + /// + public interface IIdentityRole : IComponent + { + } +} diff --git a/src/WebExpress.WebCore/WebIdentity/IIdentityRoleContext.cs b/src/WebExpress.WebCore/WebIdentity/IIdentityRoleContext.cs new file mode 100644 index 0000000..08fea06 --- /dev/null +++ b/src/WebExpress.WebCore/WebIdentity/IIdentityRoleContext.cs @@ -0,0 +1,27 @@ +using WebExpress.WebCore.WebApplication; +using WebExpress.WebCore.WebComponent; +using WebExpress.WebCore.WebPlugin; + +namespace WebExpress.WebCore.WebIdentity +{ + /// + /// Defines the context for a role, providing access to various related contexts and properties. + /// + public interface IIdentityRoleContext : IContext + { + /// + /// Returns the role id. + /// + IComponentId RoleId { get; } + + /// + /// Returns the associated plugin context. + /// + IPluginContext PluginContext { get; } + + /// + /// Returns the corresponding application context. + /// + IApplicationContext ApplicationContext { get; } + } +} diff --git a/src/WebExpress.WebCore/WebIdentity/IdentityManager.cs b/src/WebExpress.WebCore/WebIdentity/IdentityManager.cs new file mode 100644 index 0000000..9b2dea0 --- /dev/null +++ b/src/WebExpress.WebCore/WebIdentity/IdentityManager.cs @@ -0,0 +1,587 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Runtime.InteropServices; +using System.Security; +using System.Security.Cryptography; +using System.Text; +using WebExpress.WebCore.Internationalization; +using WebExpress.WebCore.WebApplication; +using WebExpress.WebCore.WebAttribute; +using WebExpress.WebCore.WebComponent; +using WebExpress.WebCore.WebIdentity.Model; +using WebExpress.WebCore.WebMessage; +using WebExpress.WebCore.WebPlugin; +using WebExpress.WebCore.WebSession.Model; + +namespace WebExpress.WebCore.WebIdentity +{ + /// + /// Management of identities (users). + /// + public class IdentityManager : IIdentityManager + { + private readonly IComponentHub _componentHub; + private readonly IHttpServerContext _httpServerContext; + private readonly IdentityPermissionDictionary _permissionDictionary = []; + private readonly IdentityRoleDictionary _roleDictionary = []; + + /// + /// Returns all permissions. + /// + public IEnumerable Permissions => _permissionDictionary.Values + .SelectMany(x => x.Values) + .SelectMany(x => x) + .Select(x => x.PermissionContext); + + /// + /// Returns all roles. + /// + public IEnumerable Roles => _roleDictionary.Values + .SelectMany(x => x.Values) + .SelectMany(x => x) + .Select(x => x.RoleContext); + + /// + /// Returns all identities. + /// + public IEnumerable Identities => []; + + /// + /// Returns the current signed-in identity. + /// + public IIdentity CurrentIdentity { get; private set; } + + /// + /// Initializes a new instance of the class. + /// + /// The component hub. + /// The reference to the context of the host. + [SuppressMessage("CodeQuality", "IDE0051:Remove unused private members", Justification = "Used via Reflection.")] + private IdentityManager(IComponentHub componentHub, IHttpServerContext httpServerContext) + { + _componentHub = componentHub; + + _componentHub.PluginManager.AddPlugin += OnAddPlugin; + _componentHub.PluginManager.RemovePlugin += OnRemovePlugin; + _componentHub.ApplicationManager.AddApplication += OnAddApplication; + _componentHub.ApplicationManager.RemoveApplication += OnRemoveApplication; + + _httpServerContext = httpServerContext; + + _httpServerContext.Log.Debug + ( + I18N.Translate + ( + "webexpress.webcore:identitymanager.initialization" + ) + ); + } + + /// + /// Discovers and binds jobs to an application. + /// + /// The context of the plugin whose jobs are to be associated. + private void Register(IPluginContext pluginContext) + { + if (_permissionDictionary.ContainsKey(pluginContext)) + { + return; + } + + Register(pluginContext, _componentHub.ApplicationManager.GetApplications(pluginContext)); + } + + /// + /// Discovers and binds jobs to an application. + /// + /// The context of the application whose jobs are to be associated. + private void Register(IApplicationContext applicationContext) + { + foreach (var pluginContext in _componentHub.PluginManager.GetPlugins(applicationContext)) + { + if (_permissionDictionary.TryGetValue(pluginContext, out var appDict) && appDict.ContainsKey(applicationContext)) + { + continue; + } + + Register(pluginContext, [applicationContext]); + } + } + + /// + /// Registers roles and ientities for a given plugin and application context. + /// + /// The plugin context. + /// The application context (optional). + private void Register(IPluginContext pluginContext, IEnumerable applicationContexts) + { + var assembly = pluginContext?.Assembly; + + // permissions + foreach (var permissionType in assembly.GetTypes().Where + ( + x => x.IsClass == true && + x.IsSealed && + x.IsPublic && + ( + x.GetInterface(typeof(IIdentityPermission).Name) != null + ) + )) + { + var id = new ComponentId(permissionType.FullName); + var roleTypes = new List(); + + foreach (var customAttribute in permissionType.CustomAttributes + .Where(x => x.AttributeType.GetInterfaces().Contains(typeof(IRoleAttribute)))) + { + if (customAttribute.AttributeType.Name == typeof(RoleAttribute<>).Name && customAttribute.AttributeType.Namespace == typeof(RoleAttribute<>).Namespace) + { + var type = customAttribute.AttributeType.GenericTypeArguments.FirstOrDefault(); + if (type != null && !roleTypes.Contains(type)) + { + roleTypes.Add(type); + } + } + } + + // assign the event to existing applications + foreach (var applicationContext in applicationContexts) + { + var permissionContext = new IdentityPermissionContext() + { + PluginContext = pluginContext, + ApplicationContext = applicationContext, + PermissionId = id, + Permission = permissionType + }; + + if (_permissionDictionary.AddPermissionItem + ( + pluginContext, + applicationContext, + new IdentityPermissionItem(_componentHub, _httpServerContext, pluginContext, applicationContext, permissionType, permissionContext, roleTypes) + )) + { + _httpServerContext.Log.Debug + ( + I18N.Translate + ( + "webexpress.webcore:identitymanager.registerpermission", + id, + applicationContext.ApplicationId + ) + ); + } + else + { + _httpServerContext.Log.Debug + ( + I18N.Translate + ( + "webexpress.webcore:identitymanager.duplicatepermission", + id, + applicationContext.ApplicationId + ) + ); + } + } + } + + // roles + foreach (var roleType in assembly.GetTypes().Where + ( + x => x.IsClass == true && + x.IsSealed && + x.IsPublic && + ( + x.GetInterface(typeof(IIdentityRole).Name) != null + ) + )) + { + var id = new ComponentId(roleType.FullName); + var permissionTypes = new List(); + + foreach (var customAttribute in roleType.CustomAttributes + .Where(x => x.AttributeType.GetInterfaces().Contains(typeof(IPermissionAttribute)))) + { + if (customAttribute.AttributeType.Name == typeof(PermissionAttribute<>).Name && customAttribute.AttributeType.Namespace == typeof(PermissionAttribute<>).Namespace) + { + var type = customAttribute.AttributeType.GenericTypeArguments.FirstOrDefault(); + if (type != null && !permissionTypes.Contains(type)) + { + permissionTypes.Add(type); + } + } + } + + // assign the event to existing applications + foreach (var applicationContext in applicationContexts) + { + var roleContext = new IdentityRoleContext() + { + PluginContext = pluginContext, + ApplicationContext = applicationContext, + RoleId = id + }; + + if (_roleDictionary.AddRoleItem + ( + pluginContext, + applicationContext, + new IdentityRoleItem(_componentHub, _httpServerContext, pluginContext, applicationContext, roleType, roleContext, permissionTypes) + )) + { + _httpServerContext.Log.Debug + ( + I18N.Translate + ( + "webexpress.webcore:identitymanager.registerrole", + id, + applicationContext.ApplicationId + ) + ); + } + else + { + _httpServerContext.Log.Debug + ( + I18N.Translate + ( + "webexpress.webcore:identitymanager.duplicaterole", + id, + applicationContext.ApplicationId + ) + ); + } + } + } + } + + /// + /// Removes all roles and permissions of an plugin. + /// + /// The context of the plugin that contains the identities to remove. + internal void Remove(IPluginContext pluginContext) + { + // permissions + if (_permissionDictionary.TryGetValue(pluginContext, out var permissionValue)) + { + foreach (var permissionItem in permissionValue + .SelectMany(x => x.Value)) + { + permissionItem.Dispose(); + } + + _permissionDictionary.Remove(pluginContext); + } + + // roles + if (_roleDictionary.TryGetValue(pluginContext, out var roleValue)) + { + foreach (var permissionItem in roleValue + .SelectMany(x => x.Value)) + { + permissionItem.Dispose(); + } + + _roleDictionary.Remove(pluginContext); + } + } + + /// + /// Removes all roles and permissions of an application. + /// + /// The context of the application that contains the identities to remove. + internal void Remove(IApplicationContext applicationContext) + { + if (applicationContext == null) + { + return; + } + + // permissions + foreach (var pluginDict in _permissionDictionary.Values) + { + foreach (var appDict in pluginDict.Where(x => x.Key == applicationContext).Select(x => x.Value)) + { + foreach (var permissionItem in appDict) + { + permissionItem.Dispose(); + } + } + + pluginDict.Remove(applicationContext); + } + + // roles + foreach (var pluginDict in _roleDictionary.Values) + { + foreach (var appDict in pluginDict.Where(x => x.Key == applicationContext).Select(x => x.Value)) + { + foreach (var roleItem in appDict) + { + roleItem.Dispose(); + } + } + + pluginDict.Remove(applicationContext); + } + } + + /// + /// Raises the event when an plugin is added. + /// + /// The source of the event. + /// The context of the plugin being added. + private void OnAddPlugin(object sender, IPluginContext e) + { + Register(e); + } + + /// + /// Raises the event when a plugin is removed. + /// + /// The source of the event. + /// The context of the plugin being removed. + private void OnRemovePlugin(object sender, IPluginContext e) + { + Remove(e); + } + + /// + /// Raises the event when an application is removed. + /// + /// The source of the event. + /// The context of the application being removed. + private void OnRemoveApplication(object sender, IApplicationContext e) + { + Remove(e); + } + + /// + /// Raises the event when an application is added. + /// + /// The source of the event. + /// The context of the application being added. + private void OnAddApplication(object sender, IApplicationContext e) + { + Register(e); + } + + /// + /// Login an identity. + /// + /// The request. + /// The identity. + /// The password. + /// True if successful, false otherwise. + public bool Login(Request request, IIdentity identity, SecureString password) + { + if (identity?.PasswordHash == ComputeHash(password)) + { + var session = _componentHub.SessionManager.GetSession(request); + var authentification = session.GetOrCreateProperty(identity); + + if (authentification.Identity != identity) + { + return false; + } + + return true; + } + + return false; + } + + /// + /// Logout an identity. + /// + /// The request. + public void Logout(Request request) + { + var session = _componentHub.SessionManager.GetSession(request); + session.RemoveProperty(); + } + + /// + /// Returns the current signed-in identity based on the provided request. + /// + /// The request to get the current identity for. + /// The current signed-in identity. + public IIdentity GetCurrentIdentity(Request request) + { + var session = _componentHub.SessionManager.GetSession(request); + var authentification = session.GetProperty(); + + return authentification?.Identity; + } + + /// + /// Checks if the specified identity has the given permission. + /// + /// The type of the identity permission. + /// The context of the application. + /// The identity to check. + /// True if the identity has the permission, false otherwise. + public bool CheckAccess(IApplicationContext applicationContext, IIdentity identity) where T : IIdentityPermission + { + return CheckAccess(applicationContext, identity, typeof(T)); + } + + /// + /// Checks if the specified identity has the given permission. + /// + /// The context of the application. + /// The identity to check. + /// The permission to check for. + /// True if the identity has the permission, false otherwise. + public bool CheckAccess(IApplicationContext applicationContext, IIdentity identity, Type permission) + { + foreach (var group in identity?.Groups ?? []) + { + if (CheckAccess(applicationContext, group, permission)) + { + return true; + } + } + + return false; + } + + /// + /// Checks if the specified identity group has the given permission. + /// + /// The type of the identity permission. + /// The context of the application. + /// The identity group to check. + /// True if the identity group has the permission, false otherwise. + public bool CheckAccess(IApplicationContext applicationContext, IIdentityGroup group) where T : IIdentityPermission + { + return CheckAccess(applicationContext, group, typeof(T)); + } + + /// + /// Checks if the specified identity group has the given permission. + /// + /// The identity group to check. + /// The context of the application. + /// The permission to check for. + /// True if the identity group has the permission, false otherwise. + public bool CheckAccess(IApplicationContext applicationContext, IIdentityGroup group, Type permission) + { + foreach (var role in group?.Roles ?? []) + { + if (CheckAccess(applicationContext, role, permission)) + { + return true; + } + } + + return false; + } + + /// + /// Checks if the specified identity role has the given permission. + /// + /// The type of the identity role. + /// The type of the identity permission. + /// The context of the application. + /// True if the identity role has the permission, false otherwise. + public bool CheckAccess(IApplicationContext applicationContext) where R : IIdentityRole where P : IIdentityPermission + { + return CheckAccess(applicationContext, typeof(R), typeof(P)); + } + /// + /// Checks if the specified identity role has the given permission. + /// + /// The context of the application. + /// The identity role to check. + /// The permission to check for. + /// True if the identity role has the permission, false otherwise. + public bool CheckAccess(IApplicationContext applicationContext, Type roleType, Type permissionType) + { + return CheckAccess(applicationContext, roleType.FullName.ToLower(), permissionType); + } + + /// + /// Checks if the specified identity role has the given permission. + /// + /// The context of the application. + /// The identity role to check. + /// The permission to check for. + /// True if the identity role has the permission, false otherwise. + private bool CheckAccess(IApplicationContext applicationContext, string roleName, Type permissionType) + { + // roles to permissions + var roles = _roleDictionary.Values.SelectMany(x => x) + .Where(x => x.Key == applicationContext) + .SelectMany(entry => entry.Value); + + foreach (var role in roles.Where(x => x.RoleClass.FullName.Equals(roleName, StringComparison.CurrentCultureIgnoreCase))) + { + if (role.Permissions.Contains(permissionType)) + { + return true; + } + } + + // permissions to roles + var permissions = _permissionDictionary.Values.SelectMany(x => x) + .Where(x => x.Key == applicationContext) + .SelectMany(entry => entry.Value); + + foreach (var permission in permissions.Where(x => x.PermissionClass == permissionType)) + { + if (permission.Roles.Any(x => x.FullName.Equals(roleName, StringComparison.CurrentCultureIgnoreCase))) + { + return true; + } + } + + return false; + } + + /// + /// Computes the SHA-256 hash of the input string. + /// + /// The input string to hash. + /// The computed hash as a hexadecimal string. + public static string ComputeHash(SecureString input) + { + var bstr = IntPtr.Zero; + try + { + bstr = Marshal.SecureStringToBSTR(input); + var length = Marshal.ReadInt32(bstr, -4); + var bytes = new byte[length]; + Marshal.Copy(bstr, bytes, 0, length); + var hashBytes = SHA256.HashData(bytes); + var builder = new StringBuilder(); + + foreach (var b in hashBytes) + { + builder.Append(b.ToString("x2")); + } + + return builder.ToString(); + } + finally + { + if (bstr != IntPtr.Zero) + { + Marshal.ZeroFreeBSTR(bstr); + } + } + } + + /// + /// Release of unmanaged resources reserved during use. + /// + public void Dispose() + { + GC.SuppressFinalize(this); + } + } +} diff --git a/src/WebExpress.WebCore/WebIdentity/IdentityPermissionContext.cs b/src/WebExpress.WebCore/WebIdentity/IdentityPermissionContext.cs new file mode 100644 index 0000000..f7daaf3 --- /dev/null +++ b/src/WebExpress.WebCore/WebIdentity/IdentityPermissionContext.cs @@ -0,0 +1,42 @@ +using System; +using WebExpress.WebCore.WebApplication; +using WebExpress.WebCore.WebComponent; +using WebExpress.WebCore.WebPlugin; + +namespace WebExpress.WebCore.WebIdentity +{ + /// + /// Defines the context for a permission, providing access to various related contexts and properties. + /// + public class IdentityPermissionContext : IIdentityPermissionContext + { + /// + /// Returns the permission id. + /// + public IComponentId PermissionId { get; internal set; } + + /// + /// Returns the permission. + /// + public Type Permission { get; internal set; } + + /// + /// Returns the associated plugin context. + /// + public IPluginContext PluginContext { get; internal set; } + + /// + /// Returns the corresponding application context. + /// + public IApplicationContext ApplicationContext { get; internal set; } + + /// + /// Returns a string that represents the current object. + /// + /// A string that represents the current object. + public override string ToString() + { + return $"Permission: {PermissionId}"; + } + } +} diff --git a/src/WebExpress.WebCore/WebIdentity/IdentityRoleContext.cs b/src/WebExpress.WebCore/WebIdentity/IdentityRoleContext.cs new file mode 100644 index 0000000..47683e4 --- /dev/null +++ b/src/WebExpress.WebCore/WebIdentity/IdentityRoleContext.cs @@ -0,0 +1,36 @@ +using WebExpress.WebCore.WebApplication; +using WebExpress.WebCore.WebComponent; +using WebExpress.WebCore.WebPlugin; + +namespace WebExpress.WebCore.WebIdentity +{ + /// + /// Defines the context for a role, providing access to various related contexts and properties. + /// + public class IdentityRoleContext : IIdentityRoleContext + { + /// + /// Returns the role id. + /// + public IComponentId RoleId { get; internal set; } + + /// + /// Returns the associated plugin context. + /// + public IPluginContext PluginContext { get; internal set; } + + /// + /// Returns the corresponding application context. + /// + public IApplicationContext ApplicationContext { get; internal set; } + + /// + /// Returns a string that represents the current object. + /// + /// A string that represents the current object. + public override string ToString() + { + return $"Role: {RoleId}"; + } + } +} diff --git a/src/WebExpress.WebCore/WebIdentity/Model/IdentityItem.cs b/src/WebExpress.WebCore/WebIdentity/Model/IdentityItem.cs new file mode 100644 index 0000000..5ad80ca --- /dev/null +++ b/src/WebExpress.WebCore/WebIdentity/Model/IdentityItem.cs @@ -0,0 +1,9 @@ +namespace WebExpress.WebCore.WebIdentity.Model +{ + /// + /// Represents an identity item within the WebExpress framework. + /// + public class IdentityItem + { + } +} diff --git a/src/WebExpress.WebCore/WebIdentity/Model/IdentityPermissionDictionary.cs b/src/WebExpress.WebCore/WebIdentity/Model/IdentityPermissionDictionary.cs new file mode 100644 index 0000000..3ddc71f --- /dev/null +++ b/src/WebExpress.WebCore/WebIdentity/Model/IdentityPermissionDictionary.cs @@ -0,0 +1,140 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using WebExpress.WebCore.WebApplication; +using WebExpress.WebCore.WebPlugin; + +namespace WebExpress.WebCore.WebIdentity.Model +{ + /// + /// The identity permission directory. + /// + internal class IdentityPermissionDictionary : Dictionary>> + { + /// + /// Adds a permission item to the dictionary. + /// + /// The plugin context. + /// The application context. + /// The permission item. + /// True if the permission item was successfully added, false if an item with the same permission class already exists. + public bool AddPermissionItem(IPluginContext pluginContext, IApplicationContext applicationContext, IdentityPermissionItem permissionItem) + { + var type = permissionItem.PermissionClass; + + if (!typeof(IIdentityPermission).IsAssignableFrom(type)) + { + return false; + } + + if (!TryGetValue(pluginContext, out var appContextDict)) + { + appContextDict = new Dictionary>(); + this[pluginContext] = appContextDict; + } + + if (!appContextDict.TryGetValue(applicationContext, out var permissionList)) + { + permissionList = new List(); + appContextDict[applicationContext] = permissionList; + } + + if (permissionList.Any(x => x.PermissionClass == type)) + { + return false; // an item with the same permission class already exists + } + + permissionList.Add(permissionItem); + + return true; + } + + /// + /// Removes a permission item from the dictionary. + /// + /// The plugin context. + /// The application context. + public void RemovePermissionItem(IPluginContext pluginContext, IApplicationContext applicationContext) + where TIdentityPermission : IIdentityPermission + { + var type = typeof(TIdentityPermission); + + if (ContainsKey(pluginContext)) + { + var appContextDict = this[pluginContext]; + + if (appContextDict.ContainsKey(applicationContext)) + { + var permissionList = appContextDict[applicationContext]; + + var itemToRemove = permissionList.FirstOrDefault(x => x.PermissionClass == type); + if (itemToRemove != null) + { + permissionList.Remove(itemToRemove); + + if (permissionList.Count == 0) + { + appContextDict.Remove(applicationContext); + + if (appContextDict.Count == 0) + { + Remove(pluginContext); + } + } + } + } + } + } + + /// + /// Returns the permission items from the dictionary. + /// + /// The type of the permission. + /// The application context. + /// An IEnumerable of permission items + public IEnumerable GetPermissionItems(IApplicationContext applicationContext) + where TIdentityPermission : IIdentityPermission + { + return GetPermissionItems(applicationContext, typeof(TIdentityPermission)); + } + + /// + /// Returns the permission items from the dictionary. + /// + /// The application context. + /// The type of the permission. + /// An IEnumerable of permission items + public IEnumerable GetPermissionItems(IApplicationContext applicationContext, Type permissionType) + { + if (!typeof(IIdentityPermission).IsAssignableFrom(permissionType)) + { + return []; + } + + if (ContainsKey(applicationContext?.PluginContext)) + { + var appContextDict = this[applicationContext?.PluginContext]; + + if (appContextDict.ContainsKey(applicationContext)) + { + var permissionList = appContextDict[applicationContext]; + + return permissionList.Where(x => x.PermissionClass == permissionType); + } + } + + return Enumerable.Empty(); + } + + /// + /// Returns all permission contexts for a given plugin context. + /// + /// The plugin context. + /// An IEnumerable of permission contexts. + public IEnumerable GetPermissionContexts(IPluginContext pluginContext) + { + return this.Where(entry => entry.Key == pluginContext) + .SelectMany(entry => entry.Value.Keys); + } + } +} diff --git a/src/WebExpress.WebCore/WebIdentity/Model/IdentityPermissionItem.cs b/src/WebExpress.WebCore/WebIdentity/Model/IdentityPermissionItem.cs new file mode 100644 index 0000000..85f7e18 --- /dev/null +++ b/src/WebExpress.WebCore/WebIdentity/Model/IdentityPermissionItem.cs @@ -0,0 +1,87 @@ +using System; +using System.Collections.Generic; +using WebExpress.WebCore.WebApplication; +using WebExpress.WebCore.WebComponent; +using WebExpress.WebCore.WebPlugin; + +namespace WebExpress.WebCore.WebIdentity.Model +{ + /// + /// Represents an identity permission item used in the web identity system. + /// + public class IdentityPermissionItem + { + private readonly IComponentHub _componentHub; + + /// + /// Returns the associated plugin context. + /// + public IPluginContext PluginContext { get; private set; } + + /// + /// Returns the corresponding application context. + /// + public IApplicationContext ApplicationContext { get; private set; } + + /// + /// Returns the roles associated with the permission. + /// + public IEnumerable Roles { get; private set; } + + /// + /// Returns or sets the permission context. + /// + public IIdentityPermissionContext PermissionContext { get; private set; } + + /// + /// Returns or sets the permission class. + /// + public Type PermissionClass { get; private set; } + + /// + /// Returns the permission instance. + /// + public IIdentityPermission Instance { get; } + + /// + /// Initializes a new instance of the class. + /// + /// The associated component hub. + /// The reference to the context of the host. + /// The associated plugin context. + /// The corresponding application context. + /// The permission class. + /// The permission context. + /// The roles associated with the permission. + public IdentityPermissionItem(IComponentHub componentHub, IHttpServerContext httpServerContext, IPluginContext pluginContext, IApplicationContext applicationContext, Type permissionClass, IIdentityPermissionContext permissionContext, IEnumerable roles) + { + _componentHub = componentHub; + PluginContext = pluginContext; + ApplicationContext = applicationContext; + Roles = roles; + PermissionClass = permissionClass; + PermissionContext = permissionContext; + Instance = ComponentActivator.CreateInstance(httpServerContext, _componentHub, PermissionClass, PermissionContext); + } + + /// + /// Performs application-specific tasks related to sharing, returning, or resetting unmanaged resources. + /// + public void Dispose() + { + if (Instance is IDisposable disposable) + { + disposable.Dispose(); + } + } + + /// + /// Convert the resource element to a string. + /// + /// The event element in its string representation. + public override string ToString() + { + return $"Permission: '{PermissionClass.FullName.ToLower()}'"; + } + } +} diff --git a/src/WebExpress.WebCore/WebIdentity/Model/IdentityRoleDictionary.cs b/src/WebExpress.WebCore/WebIdentity/Model/IdentityRoleDictionary.cs new file mode 100644 index 0000000..8097df8 --- /dev/null +++ b/src/WebExpress.WebCore/WebIdentity/Model/IdentityRoleDictionary.cs @@ -0,0 +1,155 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using WebExpress.WebCore.WebApplication; +using WebExpress.WebCore.WebPlugin; + +namespace WebExpress.WebCore.WebIdentity.Model +{ + /// + /// The identity role directory. + /// + internal class IdentityRoleDictionary : Dictionary>> + { + /// + /// Adds a role item to the dictionary. + /// + /// The plugin context. + /// The application context. + /// The role item. + /// True if the role item was successfully added, false if an item with the same role class already exists. + public bool AddRoleItem(IPluginContext pluginContext, IApplicationContext applicationContext, IdentityRoleItem roleItem) + { + var type = roleItem.RoleClass; + + if (!typeof(IIdentityRole).IsAssignableFrom(type)) + { + return false; + } + + if (!TryGetValue(pluginContext, out var appContextDict)) + { + appContextDict = []; + this[pluginContext] = appContextDict; + } + + if (!appContextDict.TryGetValue(applicationContext, out var roleList)) + { + roleList = new List(); + appContextDict[applicationContext] = roleList; + } + + if (roleList.Any(x => x.RoleClass == type)) + { + return false; // an item with the same role class already exists + } + + roleList.Add(roleItem); + + return true; + } + + /// + /// Removes a role item from the dictionary. + /// + /// The plugin context. + /// The application context. + public void RemoveRoleItem(IPluginContext pluginContext, IApplicationContext applicationContext) + where TIdentityRole : IIdentityRole + { + var type = typeof(TIdentityRole); + + if (ContainsKey(pluginContext)) + { + var appContextDict = this[pluginContext]; + + if (appContextDict.ContainsKey(applicationContext)) + { + var roleList = appContextDict[applicationContext]; + + var itemToRemove = roleList.FirstOrDefault(x => x.RoleClass == type); + if (itemToRemove != null) + { + roleList.Remove(itemToRemove); + + if (roleList.Count == 0) + { + appContextDict.Remove(applicationContext); + + if (appContextDict.Count == 0) + { + Remove(pluginContext); + } + } + } + } + } + } + + /// + /// Returns the role items from the dictionary. + /// + /// The type of the role. + /// The application context. + /// An IEnumerable of role items + public IEnumerable GetRoleItems(IApplicationContext applicationContext) + where TIdentityRole : IIdentityRole + { + return GetRoleItems(applicationContext, typeof(TIdentityRole)); + } + + /// + /// Returns the role items from the dictionary. + /// + /// The application context. + /// The type of the role. + /// An IEnumerable of role items + public IEnumerable GetRoleItems(IApplicationContext applicationContext, Type roleType) + { + if (!typeof(IIdentityRole).IsAssignableFrom(roleType)) + { + return Enumerable.Empty(); + } + + if (ContainsKey(applicationContext?.PluginContext)) + { + var appContextDict = this[applicationContext?.PluginContext]; + + if (appContextDict.ContainsKey(applicationContext)) + { + var roleList = appContextDict[applicationContext]; + + return roleList.Where(x => x.RoleClass == roleType); + } + } + + return Enumerable.Empty(); + } + + /// + /// Returns all role contexts for a given plugin context. + /// + /// The plugin context. + /// An IEnumerable of role contexts. + public IEnumerable GetRoleContexts(IPluginContext pluginContext) + { + return this.Where(x => x.Key == pluginContext) + .SelectMany(x => x.Value) + .SelectMany(x => x.Value) + .Select(x => x.RoleContext); + } + + /// + /// Returns all role contexts for a given application context. + /// + /// The application context. + /// An IEnumerable of role contexts. + public IEnumerable GetRoleContexts(IApplicationContext applicationContext) + { + return Values.SelectMany(x => x) + .Where(x => x.Key == applicationContext) + .SelectMany(entry => entry.Value) + .Select(x => x.RoleContext); + } + } +} diff --git a/src/WebExpress.WebCore/WebIdentity/Model/IdentityRoleItem.cs b/src/WebExpress.WebCore/WebIdentity/Model/IdentityRoleItem.cs new file mode 100644 index 0000000..488530b --- /dev/null +++ b/src/WebExpress.WebCore/WebIdentity/Model/IdentityRoleItem.cs @@ -0,0 +1,87 @@ +using System; +using System.Collections.Generic; +using WebExpress.WebCore.WebApplication; +using WebExpress.WebCore.WebComponent; +using WebExpress.WebCore.WebPlugin; + +namespace WebExpress.WebCore.WebIdentity.Model +{ + /// + /// Represents an item in the identity role. + /// + public class IdentityRoleItem + { + private readonly IComponentHub _componentHub; + + /// + /// Returns the associated plugin context. + /// + public IPluginContext PluginContext { get; private set; } + + /// + /// Returns the corresponding application context. + /// + public IApplicationContext ApplicationContext { get; private set; } + + /// + /// Returns or sets the role context. + /// + public IIdentityRoleContext RoleContext { get; private set; } + + /// + /// Returns the permissions associated with the role. + /// + public IEnumerable Permissions { get; private set; } + + /// + /// Returns or sets the role class. + /// + public Type RoleClass { get; private set; } + + /// + /// Returns the role instance. + /// + public IIdentityRole Instance { get; } + + /// + /// Initializes a new instance of the class. + /// + /// The associated component hub. + /// The reference to the context of the host. + /// The associated plugin context. + /// The corresponding application context. + /// The role class. + /// The role context. + /// The permissions associated with the role. + public IdentityRoleItem(IComponentHub componentHub, IHttpServerContext httpServerContext, IPluginContext pluginContext, IApplicationContext applicationContext, Type permissionClass, IIdentityRoleContext roleContext, IEnumerable permissions) + { + _componentHub = componentHub; + PluginContext = pluginContext; + ApplicationContext = applicationContext; + Permissions = permissions; + RoleClass = permissionClass; + RoleContext = roleContext; + Instance = ComponentActivator.CreateInstance(httpServerContext, _componentHub, RoleClass, RoleContext); + } + + /// + /// Performs application-specific tasks related to sharing, returning, or resetting unmanaged resources. + /// + public void Dispose() + { + if (Instance is IDisposable disposable) + { + disposable.Dispose(); + } + } + + /// + /// Convert the resource element to a string. + /// + /// The event element in its string representation. + public override string ToString() + { + return $"Role: '{RoleClass.FullName.ToLower()}'"; + } + } +} diff --git a/src/WebExpress.WebCore/WebJob/Clock.cs b/src/WebExpress.WebCore/WebJob/Clock.cs index e0dc26e..a607027 100644 --- a/src/WebExpress.WebCore/WebJob/Clock.cs +++ b/src/WebExpress.WebCore/WebJob/Clock.cs @@ -3,55 +3,55 @@ namespace WebExpress.WebCore.WebJob { + /// + /// Represents a clock that provides time-related properties and methods. + /// public class Clock { - /// - /// The underlying date and time. - /// - private DateTime DateTime { get; set; } + private DateTime _dateTime; /// /// The minute 0-59. /// - public int Minute => DateTime.Minute; + public int Minute => _dateTime.Minute; /// /// The hour 0-23. /// - public int Hour => DateTime.Hour; + public int Hour => _dateTime.Hour; /// /// The day 1-31. /// - public int Day => DateTime.Day; + public int Day => _dateTime.Day; /// /// The month 1-12. /// - public int Month => DateTime.Month; + public int Month => _dateTime.Month; /// /// The weekday 0-6 (Sunday-Saturday). /// - public int Weekday => (int)DateTime.DayOfWeek; + public int Weekday => (int)_dateTime.DayOfWeek; /// - /// Constructor + /// Initializes a new instance of the class. /// public Clock() { var dateTime = DateTime.Now; - DateTime = new DateTime(dateTime.Year, dateTime.Month, dateTime.Day, dateTime.Hour, dateTime.Minute, 0); + _dateTime = new DateTime(dateTime.Year, dateTime.Month, dateTime.Day, dateTime.Hour, dateTime.Minute, 0); } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The time to copy. public Clock(DateTime dateTime) { - DateTime = new DateTime(dateTime.Year, dateTime.Month, dateTime.Day, dateTime.Hour, dateTime.Minute, 0); + _dateTime = new DateTime(dateTime.Year, dateTime.Month, dateTime.Day, dateTime.Hour, dateTime.Minute, 0); } /// @@ -60,7 +60,7 @@ public Clock(DateTime dateTime) /// The clock to be copied. public Clock(Clock clock) { - DateTime = clock.DateTime; + _dateTime = clock._dateTime; } /// @@ -68,7 +68,7 @@ public Clock(Clock clock) /// internal void Tick() { - DateTime = DateTime.AddMinutes(1); + _dateTime = _dateTime.AddMinutes(1); } /// @@ -89,7 +89,7 @@ public IEnumerable Synchronize() next.Tick(); } - DateTime = new DateTime(dateTime.Year, dateTime.Month, dateTime.Day, dateTime.Hour, dateTime.Minute, 0); + _dateTime = new DateTime(dateTime.Year, dateTime.Month, dateTime.Day, dateTime.Hour, dateTime.Minute, 0); return elapsed; } @@ -124,7 +124,7 @@ public IEnumerable Synchronize() /// True wenn die linke Uhrzeit kleiner ist als die Rechte, false sonst public static bool operator <(Clock obj1, Clock obj2) { - return obj1.DateTime < obj2.DateTime; + return obj1._dateTime < obj2._dateTime; } /// @@ -135,7 +135,7 @@ public IEnumerable Synchronize() /// True if the time on the left is greater than the time on the right, false otherwise. public static bool operator >(Clock obj1, Clock obj2) { - return obj1.DateTime > obj2.DateTime; + return obj1._dateTime > obj2._dateTime; } /// @@ -189,7 +189,7 @@ public override bool Equals(object obj) /// The hash code. public override int GetHashCode() { - return DateTime.GetHashCode(); + return _dateTime.GetHashCode(); } } } diff --git a/src/WebExpress.WebCore/WebJob/Cron.cs b/src/WebExpress.WebCore/WebJob/Cron.cs index 684c3db..2118480 100644 --- a/src/WebExpress.WebCore/WebJob/Cron.cs +++ b/src/WebExpress.WebCore/WebJob/Cron.cs @@ -8,7 +8,9 @@ namespace WebExpress.WebCore.WebJob /// /// Manages commands that are executed on a scheduled basis (CRON = command run on notice). /// - /// + /// + /// For more information, see Cron. + /// public class Cron { /// @@ -17,32 +19,32 @@ public class Cron private static IHttpServerContext Context { get; set; } /// - /// The minute 0-59 or * for any. Comma seperated values or ranges (-) are also possible. + /// Returns the minute 0-59 or * for any. Comma seperated values or ranges (-) are also possible. /// - private List Minute { get; } = new List(); + public IEnumerable Minute { get; private set; } /// - /// The hour 0-23 or * for any. Comma seperated values or ranges (-) are also possible. + /// Returns the hour 0-23 or * for any. Comma seperated values or ranges (-) are also possible. /// - private List Hour { get; } = new List(); + public IEnumerable Hour { get; private set; } /// - /// The day 1-31 or * for any. Comma seperated values or ranges (-) are also possible. + /// Returns the day 1-31 or * for any. Comma seperated values or ranges (-) are also possible. /// - private List Day { get; } = new List(); + public IEnumerable Day { get; private set; } /// - /// The month 1-12 or * for any. Comma seperated values or ranges (-) are also possible. + /// Returns the month 1-12 or * for any. Comma seperated values or ranges (-) are also possible. /// - private List Month { get; } = new List(); + public IEnumerable Month { get; private set; } /// - /// The day of the week 0-6 (Sunday-Saturday) or * for any. Comma seperated values or ranges (-) are also possible. + /// Returns the day of the week 0-6 (Sunday-Saturday) or * for any. Comma seperated values or ranges (-) are also possible. /// - private List Weekday { get; } = new List(); + public IEnumerable Weekday { get; private set; } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The reference to the host's context. /// The minute 0-59 or * for any. Comma seperated values or ranges (-) are also possible. @@ -54,11 +56,11 @@ public Cron(IHttpServerContext context, string minute = "*", string hour = "*", { Context = context; - Minute.AddRange(Parse(minute, 0, 60)); - Hour.AddRange(Parse(hour, 0, 24)); - Day.AddRange(Parse(day, 1, 31)); - Month.AddRange(Parse(month, 1, 12)); - Weekday.AddRange(Parse(weekday, 0, 7)); + Minute = Parse(minute, 0, 60); + Hour = Parse(hour, 0, 24); + Day = Parse(day, 1, 31); + Month = Parse(month, 1, 12); + Weekday = Parse(weekday, 0, 7); } /// @@ -68,7 +70,7 @@ public Cron(IHttpServerContext context, string minute = "*", string hour = "*", /// The minimum. /// The maximum. /// The parsed values. - private IEnumerable Parse(string value, int minValue, int maxValue) + private static IEnumerable Parse(string value, int minValue, int maxValue) { var items = new List() as IEnumerable; value = value?.ToLower().Trim(); @@ -109,7 +111,7 @@ private IEnumerable Parse(string value, int minValue, int maxValue) } else { - Context.Log.Warning(message: InternationalizationManager.I18N("webexpress:schedulermanager.cron.range"), args: value); + Context.Log.Warning(message: I18N.Translate("webexpress.webcore:schedulermanager.cron.range"), args: value); } } else if (range.Length == 1) @@ -118,21 +120,21 @@ private IEnumerable Parse(string value, int minValue, int maxValue) { if (result >= minValue && result <= maxValue) { - items = items.Union(new List { result }); + items = items.Union([result]); } else { - Context.Log.Warning(message: InternationalizationManager.I18N("webexpress:schedulermanager.cron.range"), args: result); + Context.Log.Warning(message: I18N.Translate("webexpress.webcore:schedulermanager.cron.range"), args: result); } } else { - Context.Log.Warning(message: InternationalizationManager.I18N("webexpress:schedulermanager.cron.parseerror"), args: value); + Context.Log.Warning(message: I18N.Translate("webexpress.webcore:schedulermanager.cron.parseerror"), args: value); } } else { - Context.Log.Warning(message: InternationalizationManager.I18N("webexpress:schedulermanager.cron.parseerror"), args: value); + Context.Log.Warning(message: I18N.Translate("webexpress.webcore:schedulermanager.cron.parseerror"), args: value); } } diff --git a/src/WebExpress.WebCore/WebJob/IJob.cs b/src/WebExpress.WebCore/WebJob/IJob.cs index 91f82fc..f84bd0e 100644 --- a/src/WebExpress.WebCore/WebJob/IJob.cs +++ b/src/WebExpress.WebCore/WebJob/IJob.cs @@ -1,20 +1,14 @@ -using WebExpress.WebCore.Internationalization; +using WebExpress.WebCore.WebComponent; namespace WebExpress.WebCore.WebJob { /// /// A task that can be performed cyclically. /// - public interface IJob : II18N + public interface IJob : IComponent { /// - /// Initialization - /// - /// The context in which the job is executed. - public void Initialization(IJobContext context); - - /// - /// Processing of the resource. + /// Processing of the job. /// public void Process(); diff --git a/src/WebExpress.WebCore/WebJob/IJobContext.cs b/src/WebExpress.WebCore/WebJob/IJobContext.cs index f99bcb9..ae45b1d 100644 --- a/src/WebExpress.WebCore/WebJob/IJobContext.cs +++ b/src/WebExpress.WebCore/WebJob/IJobContext.cs @@ -1,9 +1,13 @@ -using WebExpress.WebCore.WebModule; +using WebExpress.WebCore.WebApplication; +using WebExpress.WebCore.WebComponent; using WebExpress.WebCore.WebPlugin; namespace WebExpress.WebCore.WebJob { - public interface IJobContext + /// + /// Represents the context of a job. + /// + public interface IJobContext : IContext { /// /// Returns the associated plugin context. @@ -11,14 +15,14 @@ public interface IJobContext IPluginContext PluginContext { get; } /// - /// Returns the corresponding module context. + /// Returns the corresponding application context. /// - IModuleContext ModuleContext { get; } + IApplicationContext ApplicationContext { get; } /// /// Returns the job id. /// - string JobId { get; } + IComponentId JobId { get; } /// /// Returns the cron-object. diff --git a/src/WebExpress.WebCore/WebJob/IJobManager.cs b/src/WebExpress.WebCore/WebJob/IJobManager.cs new file mode 100644 index 0000000..78496d5 --- /dev/null +++ b/src/WebExpress.WebCore/WebJob/IJobManager.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using WebExpress.WebCore.WebApplication; +using WebExpress.WebCore.WebComponent; + +namespace WebExpress.WebCore.WebJob +{ + /// + /// Processing of cyclic jobs. + /// + public interface IJobManager : IComponentManager + { + /// + /// Returns all jobs contextes. + /// + IEnumerable Jobs { get; } + + /// + /// Returns a JobContext instance associated with an application. + /// + /// The context of the application. + /// The type of the job. + /// A JobContext instance. + IJobContext GetJob(IApplicationContext applicationContext, Type jobType); + } +} diff --git a/src/WebExpress.WebCore/WebJob/Job.cs b/src/WebExpress.WebCore/WebJob/Job.cs index b4e9a11..64eee59 100644 --- a/src/WebExpress.WebCore/WebJob/Job.cs +++ b/src/WebExpress.WebCore/WebJob/Job.cs @@ -1,7 +1,4 @@ - -using System.Globalization; - -namespace WebExpress.WebCore.WebJob +namespace WebExpress.WebCore.WebJob { /// /// A task that can be performed cyclically. @@ -9,30 +6,18 @@ namespace WebExpress.WebCore.WebJob public class Job : IJob { /// - /// Returns or sets the culture. - /// - public CultureInfo Culture { get; set; } - - /// - /// Returns the context. - /// - public IJobContext Context { get; private set; } - - /// - /// Initialization + /// Processing of the resource. /// - /// The context in which the job is executed. - public virtual void Initialization(IJobContext context) + public virtual void Process() { - Context = context; + } /// - /// Processing of the resource. + /// Release of unmanaged resources reserved during use. /// - public virtual void Process() + public virtual void Dispose() { - } } } diff --git a/src/WebExpress.WebCore/WebJob/JobContext.cs b/src/WebExpress.WebCore/WebJob/JobContext.cs index 80a8c3e..7f120d9 100644 --- a/src/WebExpress.WebCore/WebJob/JobContext.cs +++ b/src/WebExpress.WebCore/WebJob/JobContext.cs @@ -1,8 +1,12 @@ -using WebExpress.WebCore.WebModule; +using WebExpress.WebCore.WebApplication; +using WebExpress.WebCore.WebComponent; using WebExpress.WebCore.WebPlugin; namespace WebExpress.WebCore.WebJob { + /// + /// Represents the job context. + /// public class JobContext : IJobContext { /// @@ -11,37 +15,18 @@ public class JobContext : IJobContext public IPluginContext PluginContext { get; internal set; } /// - /// Returns the corresponding module context. + /// Returns the corresponding application context. /// - public IModuleContext ModuleContext { get; internal set; } + public IApplicationContext ApplicationContext { get; internal set; } /// /// Returns the job id. /// - public string JobId { get; internal set; } + public IComponentId JobId { get; internal set; } /// /// Returns the cron-object. /// public Cron Cron { get; internal set; } - - /// - /// Constructor - /// - /// The module context. - internal JobContext(IModuleContext moduleContext) - { - PluginContext = moduleContext?.PluginContext; - ModuleContext = moduleContext; - } - - /// - /// Constructor - /// - /// The plugin context. - internal JobContext(IPluginContext pluginContext) - { - PluginContext = pluginContext; - } } } diff --git a/src/WebExpress.WebCore/WebJob/JobManager.cs b/src/WebExpress.WebCore/WebJob/JobManager.cs index 534751f..a6fe07c 100644 --- a/src/WebExpress.WebCore/WebJob/JobManager.cs +++ b/src/WebExpress.WebCore/WebJob/JobManager.cs @@ -1,94 +1,112 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading; using System.Threading.Tasks; using WebExpress.WebCore.Internationalization; +using WebExpress.WebCore.WebApplication; using WebExpress.WebCore.WebAttribute; using WebExpress.WebCore.WebComponent; -using WebExpress.WebCore.WebModule; +using WebExpress.WebCore.WebJob.Model; using WebExpress.WebCore.WebPlugin; namespace WebExpress.WebCore.WebJob { - /// - /// Processing of cyclic jobs - /// - public sealed class JobManager : IComponentPlugin, ISystemComponent, IExecutableElements + /// + /// This class manages the processing of cyclic jobs. It provides methods to register, remove, and execute jobs. + /// + public sealed class JobManager : IJobManager, ISystemComponent, IExecutableElements { - /// - /// Thread termination. - /// - private CancellationTokenSource TokenSource { get; } = new CancellationTokenSource(); + private readonly IComponentHub _componentHub; + private readonly IHttpServerContext _httpServerContext; + private readonly ScheduleDictionary _staticScheduleDictionary = []; + private readonly List _dynamicScheduleList = []; + private readonly CancellationTokenSource _tokenSource = new(); + private readonly Clock _clock = new(); /// - /// The clock for determining the execution of the crons. + /// An event that fires when an job is added. /// - private Clock Clock { get; } = new Clock(); + public event EventHandler AddJob; /// - /// Returns or sets the reference to the context of the host. + /// An event that fires when an job is removed. /// - public IHttpServerContext HttpServerContext { get; private set; } + public event EventHandler RemoveJob; /// - /// Returns the directory where the static jobs are listed. + /// Returns all job contextes. /// - private ScheduleDictionary StaticScheduleDictionary { get; } = new ScheduleDictionary(); + public IEnumerable Jobs => _staticScheduleDictionary + .SelectMany(x => x.Value) + .SelectMany(x => x.Value) + .SelectMany(x => x.Value) + .Select(x => x.JobContext) + .Union(_dynamicScheduleList.Select(x => x.JobContext)); /// - /// Returns the directory where the dynamic jobs are listed. + /// Initializes a new instance of the class. /// - private IEnumerable DynamicScheduleList { get; set; } = new List(); + /// The component hub. + /// The reference to the context of the host. + [SuppressMessage("CodeQuality", "IDE0051:Remove unused private members", Justification = "Used via Reflection.")] + private JobManager(IComponentHub componentHub, IHttpServerContext httpServerContext) + { + _componentHub = componentHub; + + _componentHub.PluginManager.AddPlugin += OnAddPlugin; + _componentHub.PluginManager.RemovePlugin += OnRemovePlugin; + _componentHub.ApplicationManager.AddApplication += OnAddApplication; + _componentHub.ApplicationManager.RemoveApplication += OnRemoveApplication; + _httpServerContext = httpServerContext; + + _httpServerContext.Log.Debug + ( + I18N.Translate + ( + "webexpress.webcore:jobmanager.initialization" + ) + ); + } /// - /// Constructor + /// Discovers and binds static jobs to an application. /// - internal JobManager() + /// The context of the plugin whose jobs are to be associated. + private void Register(IPluginContext pluginContext) { - ComponentManager.PluginManager.AddPlugin += (sender, pluginContext) => - { - Register(pluginContext); - }; - - ComponentManager.PluginManager.RemovePlugin += (sender, pluginContext) => + if (_staticScheduleDictionary.ContainsKey(pluginContext)) { - Remove(pluginContext); - }; - - ComponentManager.ModuleManager.AddModule += (sender, moduleContext) => - { - AssignToModule(moduleContext); - }; + return; + } - ComponentManager.ModuleManager.RemoveModule += (sender, moduleContext) => - { - DetachFromModule(moduleContext); - }; + Register(pluginContext, _componentHub.ApplicationManager.GetApplications(pluginContext)); } /// - /// Initialization + /// Discovers and binds jobs to an application. /// - /// The reference to the context of the host. - public void Initialization(IHttpServerContext context) + /// The context of the application whose jobs are to be associated. + private void Register(IApplicationContext applicationContext) { - HttpServerContext = context; + foreach (var pluginContext in _componentHub.PluginManager.GetPlugins(applicationContext)) + { + if (_staticScheduleDictionary.TryGetValue(pluginContext, out var appDict) && appDict.ContainsKey(applicationContext)) + { + continue; + } - HttpServerContext.Log.Debug - ( - InternationalizationManager.I18N - ( - "webexpress:jobmanager.initialization" - ) - ); + Register(pluginContext, [applicationContext]); + } } /// - /// Discovers and registers jobs from the specified plugin. + /// Registers resources for a given plugin and application context. /// - /// A context of a plugin whose jobs are to be registered. - public void Register(IPluginContext pluginContext) + /// The plugin context. + /// The application context (optional). + private void Register(IPluginContext pluginContext, IEnumerable applicationContexts) { var assembly = pluginContext?.Assembly; @@ -101,12 +119,12 @@ public void Register(IPluginContext pluginContext) )) { var id = job.FullName?.ToLower(); + var minute = "*"; var hour = "*"; var day = "*"; var month = "*"; var weekday = "*"; - var moduleId = string.Empty; foreach (var customAttribute in job.CustomAttributes.Where(x => x.AttributeType == typeof(JobAttribute))) { @@ -117,186 +135,183 @@ public void Register(IPluginContext pluginContext) weekday = customAttribute.ConstructorArguments.Skip(4).FirstOrDefault().Value?.ToString(); } - foreach (var customAttribute in job.CustomAttributes - .Where(x => x.AttributeType.GetInterfaces().Contains(typeof(IModuleAttribute)))) + // assign the job to existing applications + foreach (var applicationContext in applicationContexts) { - if (customAttribute.AttributeType.Name == typeof(ModuleAttribute<>).Name && customAttribute.AttributeType.Namespace == typeof(ModuleAttribute<>).Namespace) + var jobContext = new JobContext() { - moduleId = customAttribute.AttributeType.GenericTypeArguments.FirstOrDefault()?.FullName?.ToLower(); - } - } + JobId = new ComponentId(job.FullName), + PluginContext = pluginContext, + ApplicationContext = applicationContext, + Cron = new Cron(_httpServerContext, minute, hour, day, month, weekday), + }; - if (string.IsNullOrWhiteSpace(moduleId)) - { - // no module specified - HttpServerContext.Log.Warning - ( - InternationalizationManager.I18N + if (job != default) + { + if (_staticScheduleDictionary.AddScheduleItem ( - "webexpress:jobmanager.moduleless", id - ) - ); - } - - // register the job - if (!StaticScheduleDictionary.ContainsKey(pluginContext)) - { - StaticScheduleDictionary.Add(pluginContext, new List()); - } - - var dictItem = StaticScheduleDictionary[pluginContext]; + pluginContext, + applicationContext, + new ScheduleItem(_componentHub, _httpServerContext, pluginContext, applicationContext, jobContext, job) + )) + { + OnAddJob(jobContext); - dictItem.Add(new ScheduleStaticItem() - { - Assembly = assembly, - JobId = id, - Type = job, - Cron = new Cron(pluginContext.Host, minute, hour, day, month, weekday), - moduleId = moduleId - }); - - HttpServerContext.Log.Debug - ( - InternationalizationManager.I18N - ( - "webexpress:jobmanager.job.register", moduleId, id - ) - ); - - // assign the job to existing modules. - foreach (var moduleContext in ComponentManager.ModuleManager.GetModules(pluginContext, moduleId)) - { - if (moduleContext.PluginContext != pluginContext) + _httpServerContext.Log.Debug + ( + I18N.Translate + ( + "webexpress.webcore:jobmanager.register", + id, + applicationContext.ApplicationId + ) + ); + } + else + { + _httpServerContext.Log.Debug + ( + I18N.Translate + ( + "webexpress.webcore:jobmanager.duplicate", + id, + applicationContext.ApplicationId + ) + ); + } + } + else { - // job is not part of the module - HttpServerContext.Log.Warning + _httpServerContext.Log.Debug ( - InternationalizationManager.I18N + I18N.Translate ( - "webexpress:jobmanager.wrongmodule", - moduleContext.ModuleId, id + "webexpress.webcore:jobmanager.jobless", + id ) ); } - - AssignToModule(moduleContext); } } } /// - /// Discovers and registers entries from the specified plugin. + /// Removes all jobs associated with the specified plugin context. /// - /// A list with plugin contexts that contain the jobs. - public void Register(IEnumerable pluginContexts) + /// The context of the plugin that contains the jobs to remove. + internal void Remove(IPluginContext pluginContext) { - foreach (var pluginContext in pluginContexts) + if (pluginContext == null) + { + return; + } + + // the plugin has not been registered in the manager + if (_staticScheduleDictionary.TryGetValue(pluginContext, out var value)) { - Register(pluginContext); + foreach (var scheduleItem in value.Values + .SelectMany(x => x.Values) + .SelectMany(x => x)) + { + OnRemoveJob(scheduleItem.JobContext); + scheduleItem.Dispose(); + } + + _staticScheduleDictionary.Remove(pluginContext); } } /// - /// Registers a job. + /// Removes all jobs associated with the specified application context. /// - /// The plugin context. - /// The cropn-object. - /// The job. - public IJob Register(IPluginContext pluginContext, Cron cron) where T : IJob + /// The context of the application that contains the jobs to remove. + internal void Remove(IApplicationContext applicationContext) { - // create context - var jobContext = new JobContext(pluginContext) + if (applicationContext == null) { - JobId = typeof(T).FullName?.ToLower(), - Cron = cron - }; - - var jobInstance = Activator.CreateInstance(typeof(T)) as IJob; - jobInstance.Initialization(jobContext); + return; + } - var item = new ScheduleDynamicItem() + foreach (var pluginDict in _staticScheduleDictionary.Values) { - JobContext = jobContext, - Instance = jobInstance - }; - - DynamicScheduleList = DynamicScheduleList.Append(item); + foreach (var appDict in pluginDict.Where(x => x.Key == applicationContext).Select(x => x.Value)) + { + foreach (var scheduleItem in appDict.Values.SelectMany(x => x)) + { + OnRemoveJob(scheduleItem.JobContext); + scheduleItem.Dispose(); + } + } - return jobInstance; + pluginDict.Remove(applicationContext); + } } /// - /// Registers a job. + /// Removes a dynamic job. /// - /// The module context. - /// The cropn-object. - /// The job. - public IJob Register(IModuleContext moduleContext, Cron cron) where T : IJob + /// The job to remove. + public void Remove(IJob job) { - // create context - var jobContext = new JobContext(moduleContext) - { - JobId = typeof(T).FullName?.ToLower(), - Cron = cron - }; - - var jobInstance = Activator.CreateInstance(typeof(T)) as IJob; - jobInstance.Initialization(jobContext); - - var item = new ScheduleDynamicItem() - { - JobContext = jobContext, - Instance = jobInstance - }; - - DynamicScheduleList = DynamicScheduleList.Append(item); + _dynamicScheduleList.RemoveAll(x => x == job); + } - return jobInstance; + /// + /// Raises the AddJob event. + /// + /// The job context. + private void OnAddJob(IJobContext jobContext) + { + AddJob?.Invoke(this, jobContext); } /// - /// Assign existing job to the module. + /// Raises the RemoveJob event. /// - /// The context of the module. - private void AssignToModule(IModuleContext moduleContext) + /// The job context. + private void OnRemoveJob(IJobContext jobContext) { - foreach (var scheduleItem in StaticScheduleDictionary.Values.SelectMany(x => x)) - { - if (scheduleItem.moduleId.Equals(moduleContext?.ModuleId)) - { - scheduleItem.AddModule(moduleContext); - } - } + RemoveJob?.Invoke(this, jobContext); } /// - /// Remove an existing modules to the job. + /// Raises the event when an plugin is added. /// - /// The context of the module. - private void DetachFromModule(IModuleContext moduleContext) + /// The source of the event. + /// The context of the plugin being added. + private void OnAddPlugin(object sender, IPluginContext e) { - foreach (var scheduleItem in StaticScheduleDictionary.Values.SelectMany(x => x)) - { - if (scheduleItem.moduleId.Equals(moduleContext?.ModuleId)) - { - scheduleItem.DetachModule(moduleContext); - } - } + Register(e); + } + + /// + /// Raises the event when a plugin is removed. + /// + /// The source of the event. + /// The context of the plugin being removed. + private void OnRemovePlugin(object sender, IPluginContext e) + { + Remove(e); } /// - /// Retruns the schedule item for a given plugin. + /// Raises the event when an application is removed. /// - /// The context of the plugin. - /// An enumeration of the schedule item for the given plugin. - internal IEnumerable GetScheduleItems(IPluginContext pluginContext) + /// The source of the event. + /// The context of the application being removed. + private void OnRemoveApplication(object sender, IApplicationContext e) { - if (pluginContext == null || !StaticScheduleDictionary.ContainsKey(pluginContext)) - { - return Enumerable.Empty(); - } + Remove(e); + } - return StaticScheduleDictionary[pluginContext]; + /// + /// Raises the event when an application is added. + /// + /// The source of the event. + /// The context of the application being added. + private void OnAddApplication(object sender, IApplicationContext e) + { + Register(e); } /// @@ -306,7 +321,7 @@ internal void Execute() { Task.Factory.StartNew(() => { - while (!TokenSource.IsCancellationRequested) + while (!_tokenSource.IsCancellationRequested) { Update(); @@ -314,7 +329,7 @@ internal void Execute() Thread.Sleep(secendsLeft * 1000); } - }, TokenSource.Token); + }, _tokenSource.Token); } /// @@ -322,19 +337,21 @@ internal void Execute() /// private void Update() { - foreach (var clock in Clock.Synchronize()) + foreach (var clock in _clock.Synchronize()) { - foreach (var scheduleItemValue in StaticScheduleDictionary.Values - .SelectMany(x => x) - .SelectMany(x => x.Dictionary.Values)) + foreach (var scheduleItemValue in _staticScheduleDictionary + .SelectMany(x => x.Value) + .SelectMany(x => x.Value) + .SelectMany(x => x.Value) + .Union(_dynamicScheduleList.Select(x => x))) { - if (scheduleItemValue.JobContext.Cron.Matching(Clock)) + if (scheduleItemValue.JobContext.Cron.Matching(_clock)) { - HttpServerContext.Log.Debug + _httpServerContext.Log.Debug ( - InternationalizationManager.I18N + I18N.Translate ( - "webexpress:jobmanager.job.process", + "webexpress.webcore:jobmanager.job.process", scheduleItemValue.JobContext.JobId ) ); @@ -342,19 +359,19 @@ private void Update() Task.Factory.StartNew(() => { scheduleItemValue.Instance?.Process(); - }, TokenSource.Token); + }, _tokenSource.Token); } } - foreach (var scheduleItemValue in DynamicScheduleList) + foreach (var scheduleItemValue in _dynamicScheduleList) { - if (scheduleItemValue.JobContext.Cron.Matching(Clock)) + if (scheduleItemValue.JobContext.Cron.Matching(_clock)) { - HttpServerContext.Log.Debug + _httpServerContext.Log.Debug ( - InternationalizationManager.I18N + I18N.Translate ( - "webexpress:jobmanager.job.process", + "webexpress.webcore:jobmanager.job.process", scheduleItemValue.JobContext.JobId ) ); @@ -362,70 +379,39 @@ private void Update() Task.Factory.StartNew(() => { scheduleItemValue.Instance?.Process(); - }, TokenSource.Token); + }, _tokenSource.Token); } } } } /// - /// Stop running the scheduler. + /// Returns a JobContext instance associated with an application. /// - public void ShutDown() + /// The context of the application. + /// The type of the job. + /// A JobContext instance. + public IJobContext GetJob(IApplicationContext applicationContext, Type jobType) { - TokenSource.Cancel(); + return _staticScheduleDictionary + .SelectMany(x => x.Value) + .SelectMany(x => x.Value) + .SelectMany(x => x.Value) + .Union(_dynamicScheduleList.Select(x => x)) + .FirstOrDefault(x => x.JobContext.ApplicationContext == applicationContext && x.JobClass == jobType)?.JobContext; } /// - /// Removes all jobs associated with the specified plugin context. + /// Release of unmanaged resources reserved during use. /// - /// The context of the plugin that contains the jobs to remove. - public void Remove(IPluginContext pluginContext) + public void Dispose() { - // the plugin has not been registered in the manager - if (!StaticScheduleDictionary.ContainsKey(pluginContext)) - { - return; - } + _componentHub.PluginManager.AddPlugin -= OnAddPlugin; + _componentHub.PluginManager.RemovePlugin -= OnRemovePlugin; + _componentHub.ApplicationManager.AddApplication -= OnAddApplication; + _componentHub.ApplicationManager.RemoveApplication -= OnRemoveApplication; - foreach (var scheduleItem in StaticScheduleDictionary[pluginContext]) - { - scheduleItem.Dispose(); - } - - StaticScheduleDictionary.Remove(pluginContext); - } - - /// - /// Removes a job. - /// - /// The job to remove. - public void Remove(IJob job) - { - DynamicScheduleList = DynamicScheduleList.Where(x => x != job); - } - - /// - /// Information about the component is collected and prepared for output in the log. - /// - /// The context of the plugin. - /// A list of log entries. - /// The shaft deep. - public void PrepareForLog(IPluginContext pluginContext, IList output, int deep) - { - foreach (var scheduleItem in GetScheduleItems(pluginContext)) - { - output.Add - ( - string.Empty.PadRight(deep) + - InternationalizationManager.I18N - ( - "webexpress:jobmanager.job", - scheduleItem.JobId, - scheduleItem.ModuleContext - ) - ); - } + _tokenSource.Cancel(); } } } diff --git a/src/WebExpress.WebCore/WebJob/Model/ScheduleDictionary.cs b/src/WebExpress.WebCore/WebJob/Model/ScheduleDictionary.cs new file mode 100644 index 0000000..fb2298a --- /dev/null +++ b/src/WebExpress.WebCore/WebJob/Model/ScheduleDictionary.cs @@ -0,0 +1,157 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using WebExpress.WebCore.WebApplication; +using WebExpress.WebCore.WebEvent; +using WebExpress.WebCore.WebPlugin; + +namespace WebExpress.WebCore.WebJob.Model +{ + /// + /// Represents a dictionary that provides a mapping from plugin contexts to dictionaries of application contexts and jobs. + /// key = plugin context + /// value = scheduler items + /// + internal class ScheduleDictionary : Dictionary>>> + { + /// + /// Adds a event item to the dictionary. + /// + /// The plugin context. + /// The application context. + /// The schedule item. + /// True if the schedule item was added successfully, false if an element with the same status code already exists. + public bool AddScheduleItem(IPluginContext pluginContext, IApplicationContext applicationContext, ScheduleItem scheduleItem) + { + var type = scheduleItem.JobClass; + + if (!typeof(IJob).IsAssignableFrom(type)) + { + return false; + } + + if (!ContainsKey(pluginContext)) + { + this[pluginContext] = []; + } + + var appContextDict = this[pluginContext]; + + if (!appContextDict.ContainsKey(applicationContext)) + { + appContextDict[applicationContext] = []; + } + + var scheduleDict = appContextDict[applicationContext]; + + if (!scheduleDict.ContainsKey(type)) + { + scheduleDict[type] = []; + } + + var scheduleList = scheduleDict[type]; + + if (scheduleList.Where(x => x.JobClass == type).Any()) + { + return false; // item with the same event handler already exists + } + + scheduleList.Add(scheduleItem); + + return true; + } + + /// + /// Removes a schedule item from the dictionary. + /// + /// The plugin context. + /// The application context. + public void RemoveScheduleItem(IPluginContext pluginContext, IApplicationContext applicationContext) + where TEvent : IEvent + { + var type = typeof(TEvent); + + if (ContainsKey(pluginContext)) + { + var appContextDict = this[pluginContext]; + + if (appContextDict.ContainsKey(applicationContext)) + { + var scheduleDict = appContextDict[applicationContext]; + + if (scheduleDict.ContainsKey(type)) + { + scheduleDict.Remove(type); + + if (scheduleDict.Count == 0) + { + appContextDict.Remove(applicationContext); + + if (appContextDict.Count == 0) + { + Remove(pluginContext); + } + } + } + } + } + } + + /// + /// Returns the schedule items from the dictionary. + /// + /// The type of event. + /// The application context. + /// An IEnumerable of schedule items + public IEnumerable GetScheduleItems(IApplicationContext applicationContext) + where TJob : IJob + { + return GetScheduleItems(applicationContext, typeof(TJob)); + } + + /// + /// Returns the schedule items from the dictionary. + /// + /// The application context. + /// The type of job. + /// An IEnumerable of event items + public IEnumerable GetScheduleItems(IApplicationContext applicationContext, Type jobType) + { + if (!typeof(IEvent).IsAssignableFrom(jobType)) + { + return []; + } + + if (ContainsKey(applicationContext?.PluginContext)) + { + var appContextDict = this[applicationContext?.PluginContext]; + + if (appContextDict.ContainsKey(applicationContext)) + { + var scheduleDict = appContextDict[applicationContext]; + + if (scheduleDict.ContainsKey(jobType)) + { + return scheduleDict[jobType]; + } + } + } + + return []; + } + + /// + /// Returns all job contexts for a given plugin context. + /// + /// The plugin context. + /// An IEnumerable of job contexts. + public IEnumerable GetJobs(IPluginContext pluginContext) + { + return this.Where(entry => entry.Key == pluginContext) + .SelectMany(entry => entry.Value.Values) + .SelectMany(dict => dict.Values) + .SelectMany(x => x) + .Select(x => x.JobContext); + } + } +} diff --git a/src/WebExpress.WebCore/WebJob/Model/ScheduleItem.cs b/src/WebExpress.WebCore/WebJob/Model/ScheduleItem.cs new file mode 100644 index 0000000..296f310 --- /dev/null +++ b/src/WebExpress.WebCore/WebJob/Model/ScheduleItem.cs @@ -0,0 +1,89 @@ +using System; +using System.Threading; +using WebExpress.WebCore.WebApplication; +using WebExpress.WebCore.WebComponent; +using WebExpress.WebCore.WebPlugin; + +namespace WebExpress.WebCore.WebJob.Model +{ + /// + /// Represents an appointment entry in the appointment execution directory. + /// + internal class ScheduleItem : IDisposable + { + /// + /// Returns the associated plugin context. + /// + public IPluginContext PluginContext { get; internal set; } + + /// + /// Returns the corresponding application context. + /// + public IApplicationContext ApplicationContext { get; internal set; } + + /// + /// The context associated with the job. + /// + public IJobContext JobContext { get; internal set; } + + /// + /// Returns the job class. + /// + public Type JobClass { get; internal set; } + + /// + /// Returns the job instance. + /// + public IJob Instance { get; internal set; } + + /// + /// Returns the cancel token or null if not already created. + /// + public CancellationTokenSource TokenSource { get; } = new CancellationTokenSource(); + + /// + /// Initializes a new instance of the class. + /// + /// The associated component hub. + /// The reference to the context of the host. + /// The associated plugin context. + /// The corresponding application context. + /// The job context. + /// The job class. + public ScheduleItem(IComponentHub componentHub, IHttpServerContext httpServerContext, IPluginContext pluginContext, IApplicationContext applicationContext, IJobContext jobContext, Type jobClass) + { + PluginContext = pluginContext; + ApplicationContext = applicationContext; + JobContext = jobContext; + JobClass = jobClass; + + Instance = ComponentActivator.CreateInstance + ( + jobClass, + jobContext, + httpServerContext, + componentHub, + pluginContext, + applicationContext + ); + + return; + } + + /// + /// Performs application-specific tasks related to sharing, returning, or resetting unmanaged resources. + /// + public void Dispose() + { + } + + /// + /// Convert the resource element to a string. + /// + /// The resource element in its string representation. + public override string ToString() + { + return "Job ${Id}"; + } + } +} diff --git a/src/WebExpress.WebCore/WebJob/ScheduleDictionary.cs b/src/WebExpress.WebCore/WebJob/ScheduleDictionary.cs deleted file mode 100644 index f84026b..0000000 --- a/src/WebExpress.WebCore/WebJob/ScheduleDictionary.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System.Collections.Generic; -using WebExpress.WebCore.WebPlugin; - -namespace WebExpress.WebCore.WebJob -{ - /// - /// key = plugin context - /// value = ressource items - /// - internal class ScheduleDictionary : Dictionary> - { - } -} diff --git a/src/WebExpress.WebCore/WebJob/ScheduleIDynamicItem.cs b/src/WebExpress.WebCore/WebJob/ScheduleIDynamicItem.cs deleted file mode 100644 index 6594766..0000000 --- a/src/WebExpress.WebCore/WebJob/ScheduleIDynamicItem.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System.Threading; - -namespace WebExpress.WebCore.WebJob -{ - /// - /// Represents an job entry in the dynamic job execution list. - /// - internal class ScheduleDynamicItem - { - /// - /// The context associated with the job. - /// - public IJobContext JobContext { get; set; } - - /// - /// Returns the job instance. - /// - public IJob Instance { get; internal set; } - - /// - /// Returns the cancel token or null if not already created. - /// - public CancellationTokenSource TokenSource { get; } = new CancellationTokenSource(); - } -} diff --git a/src/WebExpress.WebCore/WebJob/ScheduleStaticItem.cs b/src/WebExpress.WebCore/WebJob/ScheduleStaticItem.cs deleted file mode 100644 index 8e8c4ff..0000000 --- a/src/WebExpress.WebCore/WebJob/ScheduleStaticItem.cs +++ /dev/null @@ -1,180 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Reflection; -using WebExpress.WebCore.WebModule; -using WebExpress.WebCore.WebPlugin; - -namespace WebExpress.WebCore.WebJob -{ - /// - /// Represents an appointment entry in the appointment execution directory - /// - internal class ScheduleStaticItem - { - /// - /// The assembly that contains the module. - /// - public Assembly Assembly { get; internal set; } - - /// - /// Returns the associated plugin context. - /// - public IPluginContext PluginContext { get; internal set; } - - /// - /// Returns the corresponding module context. - /// - public IModuleContext ModuleContext { get; internal set; } - - /// - /// Returns the job id. - /// - public string JobId { get; internal set; } - - /// - /// Returns the cron object. - /// - public Cron Cron { get; internal set; } - - /// - /// Returns the log to write status messages to the console and to a log file. - /// - public Log Log { get; internal set; } - - /// - /// Returns the job class. - /// - public Type Type { get; internal set; } - - /// - /// Returns or sets the module id. - /// - public string moduleId { get; set; } - - /// - /// Returns the directory where the job instances are listed. - /// - public IDictionary Dictionary { get; } - = new Dictionary(); - - /// - /// An event that fires when an job is added. - /// - public event EventHandler AddJob; - - /// - /// An event that fires when an job is removed. - /// - public event EventHandler RemoveJob; - - /// - /// Adds an module assignment - /// - /// The context of the module. - public void AddModule(IModuleContext moduleContext) - { - // only if no instance has been created yet - if (Dictionary.ContainsKey(moduleContext)) - { - return; - } - - // Only for the right module - if (!moduleContext.ModuleId.Equals(moduleId, StringComparison.OrdinalIgnoreCase)) - { - return; - } - - // create context - var jobContext = new JobContext(moduleContext) - { - JobId = JobId, - PluginContext = PluginContext, - Cron = Cron - }; - - var jobInstance = Activator.CreateInstance(Type) as IJob; - jobInstance.Initialization(jobContext); - - Dictionary.Add - ( - moduleContext, - new ScheduleStaticItemValue() - { - JobContext = jobContext, - Instance = jobInstance - } - ); - - OnAddJob(jobContext); - } - - /// - /// Remove an module assignment - /// - /// The context of the module. - public void DetachModule(IModuleContext moduleContext) - { - // not an assignment has been created yet - if (!Dictionary.ContainsKey(moduleContext)) - { - return; - } - - foreach (var scheduleItemValue in Dictionary.Values) - { - OnRemoveResource(scheduleItemValue.JobContext); - } - - Dictionary.Remove(moduleContext); - } - - /// - /// Raises the AddJob event. - /// - /// The job context. - private void OnAddJob(IJobContext jobContext) - { - AddJob?.Invoke(this, jobContext); - } - - /// - /// Raises the RemoveJob event. - /// - /// The job context. - private void OnRemoveResource(IJobContext jobContext) - { - RemoveJob?.Invoke(this, jobContext); - } - - /// - /// Performs application-specific tasks related to sharing, returning, or resetting unmanaged resources. - /// - public void Dispose() - { - foreach (var d in AddJob.GetInvocationList()) - { - AddJob -= (EventHandler)d; - } - - foreach (var d in RemoveJob.GetInvocationList()) - { - RemoveJob -= (EventHandler)d; - } - - foreach (var scheduleItemValue in Dictionary.Values) - { - scheduleItemValue.TokenSource.Cancel(); - } - } - - /// - /// Convert the resource element to a string. - /// - /// The resource element in its string representation. - public override string ToString() - { - return "Job ${Id}"; - } - } -} diff --git a/src/WebExpress.WebCore/WebJob/ScheduleStaticItemValue.cs b/src/WebExpress.WebCore/WebJob/ScheduleStaticItemValue.cs deleted file mode 100644 index 8017517..0000000 --- a/src/WebExpress.WebCore/WebJob/ScheduleStaticItemValue.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System.Threading; - -namespace WebExpress.WebCore.WebJob -{ - /// - /// Represents an job entry in the job execution directory. - /// - internal class ScheduleStaticItemValue - { - /// - /// The context associated with the job. - /// - public IJobContext JobContext { get; set; } - - /// - /// Returns the job instance. - /// - public IJob Instance { get; internal set; } - - /// - /// Returns the cancel token or null if not already created. - /// - public CancellationTokenSource TokenSource { get; } = new CancellationTokenSource(); - } -} diff --git a/src/WebExpress.WebCore/WebLog/ILog.cs b/src/WebExpress.WebCore/WebLog/ILog.cs new file mode 100644 index 0000000..71ff833 --- /dev/null +++ b/src/WebExpress.WebCore/WebLog/ILog.cs @@ -0,0 +1,236 @@ +using Microsoft.Extensions.Logging; +using System; +using System.Runtime.CompilerServices; +using System.Text; +using WebExpress.WebCore.Setting; + +namespace WebExpress.WebCore.WebLog +{ + /// + /// Interface for logging events to your log file + /// + /// The program writes a variety of information to an event log file. The log + /// is stored in the log directory. The name consists of the date and the ending ".log". + /// The structure is designed in such a way that the log file can be read and analyzed with a text editor. + /// Error messages and notes are made available persistently in the log, so the event log files + /// are suitable for error analysis and for checking the correct functioning of the program. The minutes + /// are organized in tabular form. In the first column, the primeval time is indicated. The second + /// column defines the level of the log entry. The third column lists the function that produced the entry. + /// The last column indicates a note or error description. + /// + /// + /// Example: + /// 08:26:30 Info Program.Main Startup + /// 08:26:30 Info Program.Main -------------------------------------------------- + /// 08:26:30 Info Program.Main Version: 0.0.0.1 + /// 08:26:30 Info Program.Main Arguments: -test + /// 08:26:30 Info Program.Main Configuration version: V1 + /// 08:26:30 Info Program.Main Processing: sequentiell + /// + public interface ILog : ILogger + { + /// + /// Returns or sets the encoding. + /// + public Encoding Encoding { get; set; } + + /// + /// Determines whether to display debug output. + /// + public bool DebugMode { get; } + + /// + /// Returns the file name of the log + /// + public string Filename { get; set; } + + /// + /// Returns the number of exceptions. + /// + public int ExceptionCount { get; } + + /// + /// Returns the number of errors (errors + exceptions). + /// + public int ErrorCount { get; } + + /// + /// Returns the number of warnings. + /// + public int WarningCount { get; } + + /// + /// Checks if the log has been opened for writing. + /// + public bool IsOpen { get; } + + /// + /// Returns the log mode. + /// + public LogMode LogMode { get; set; } + + /// + /// The default instance of the logger. + /// + public static Log Current { get; } + + /// + /// Set file name time patterns. + /// + public string FilePattern { set; get; } + + /// + /// Time patternsspecifying log entries. + /// + public string TimePattern { set; get; } + + /// + /// Starts logging + /// + /// The path where the log file is created. + /// The file name of the log file. + public void Begin(string path, string name); + + /// + /// Starts logging + /// + /// The path where the log file is created. + public void Begin(string path); + + /// + /// Starts logging + /// + /// The log settings + public void Begin(SettingLogItem settings); + + /// + /// A dividing line with * characters + /// + public void Seperator(); + + /// + /// A separator with custom characters + /// + /// The separator. + public void Seperator(char sepChar); + + /// + /// Logs an info message. + /// + /// The log message. + /// >Method/ function that wants to log. + /// The line number. + /// The source file. + public void Info(string message, [CallerMemberName] string instance = null, [CallerLineNumber] int? line = null, [CallerFilePath] string file = null); + + /// + /// Logs an info message. + /// + /// The log message. + /// >Method/ function that wants to log. + /// The line number. + /// The source file. + /// Parameter für die Formatierung der Nachricht + public void Info(string message, [CallerMemberName] string instance = null, [CallerLineNumber] int? line = null, [CallerFilePath] string file = null, params object[] args); + + /// + /// Logs a warning message. + /// + /// The log message. + /// >Method/ function that wants to log. + /// The line number. + /// The source file. + public void Warning(string message, [CallerMemberName] string instance = null, [CallerLineNumber] int? line = null, [CallerFilePath] string file = null); + + /// + /// Logs a warning message. + /// + /// The log message. + /// >Method/ function that wants to log. + /// The line number. + /// The source file. + /// Parameter für die Formatierung der Nachricht + public void Warning(string message, [CallerMemberName] string instance = null, [CallerLineNumber] int? line = null, [CallerFilePath] string file = null, params object[] args); + + /// + /// Logs an error message. + /// + /// The log message. + /// >Method/ function that wants to log. + /// The line number. + /// The source file. + public void Error(string message, [CallerMemberName] string instance = null, [CallerLineNumber] int? line = null, [CallerFilePath] string file = null); + + /// + /// Logs an error message. + /// + /// The log message. + /// >Method/ function that wants to log. + /// The line number. + /// The source file. + /// Parameter für die Formatierung der Nachricht + public void Error(string message, [CallerMemberName] string instance = null, [CallerLineNumber] int? line = null, [CallerFilePath] string file = null, params object[] args); + + /// + /// Logs an error message. + /// + /// The log message. + /// >Method/ function that wants to log. + /// The line number. + /// The source file. + public void FatalError(string message, [CallerMemberName] string instance = null, [CallerLineNumber] int? line = null, [CallerFilePath] string file = null); + + /// + /// Logs an error message. + /// + /// The log message. + /// >Method/ function that wants to log. + /// The line number. + /// The source file. + /// Parameter für die Formatierung der Nachricht + public void FatalError(string message, [CallerMemberName] string instance = null, [CallerLineNumber] int? line = null, [CallerFilePath] string file = null, params object[] args); + + /// + /// Logs an exception message. + /// + /// The exception + /// >Method/ function that wants to log. + /// The line number. + /// The source file. + public void Exception(Exception ex, [CallerMemberName] string instance = null, [CallerLineNumber] int? line = null, [CallerFilePath] string file = null); + + /// + /// Logs a debug message. + /// + /// The log message. + /// >Method/ function that wants to log. + /// The line number. + /// The source file. + public void Debug(string message, [CallerMemberName] string instance = null, [CallerLineNumber] int? line = null, [CallerFilePath] string file = null); + + /// + /// Logs a debug message. + /// + /// The log message. + /// >Method/ function that wants to log. + /// The line number. + /// The source file. + /// Parameter für die Formatierung der Nachricht + public void Debug(string message, [CallerMemberName] string instance = null, [CallerLineNumber] int? line = null, [CallerFilePath] string file = null, params object[] args); + + /// + /// Stops logging. + /// + public void Close(); + + /// + /// Cleans up the log. + /// + public void Clear(); + + /// + /// Writes the contents of the queue to the log. + /// + public void Flush(); + } +} diff --git a/src/WebExpress.WebCore/WebLog/ILogManager.cs b/src/WebExpress.WebCore/WebLog/ILogManager.cs new file mode 100644 index 0000000..1f93427 --- /dev/null +++ b/src/WebExpress.WebCore/WebLog/ILogManager.cs @@ -0,0 +1,27 @@ +using System; +using WebExpress.WebCore.WebComponent; +using WebExpress.WebCore.WebPlugin; + +namespace WebExpress.WebCore.WebLog +{ + /// + /// Interface for managing logs within the web application. + /// + public interface ILogManager : IComponentManager + { + /// + /// An event that fires when a log is added. + /// + event EventHandler AddLog; + + /// + /// An event that fires when a log is removed. + /// + event EventHandler RemoveLog; + + /// + /// Returns the default log. + /// + ILog DefaultLog { get; } + } +} diff --git a/src/WebExpress.WebCore/Log.cs b/src/WebExpress.WebCore/WebLog/Log.cs similarity index 85% rename from src/WebExpress.WebCore/Log.cs rename to src/WebExpress.WebCore/WebLog/Log.cs index 8f3eab7..b437925 100644 --- a/src/WebExpress.WebCore/Log.cs +++ b/src/WebExpress.WebCore/WebLog/Log.cs @@ -8,7 +8,7 @@ using System.Threading; using WebExpress.WebCore.Setting; -namespace WebExpress.WebCore +namespace WebExpress.WebCore.WebLog { /// /// Class for logging events to your log file @@ -22,26 +22,41 @@ namespace WebExpress.WebCore /// column defines the level of the log entry. The third column lists the function that produced the entry. /// The last column indicates a note or error description. /// - /// - /// Example:
- /// 08:26:30 Info Program.Main Startup
- /// 08:26:30 Info Program.Main --------------------------------------------------
- /// 08:26:30 Info Program.Main Version: 0.0.0.1
- /// 08:26:30 Info Program.Main Arguments: -test
- /// 08:26:30 Info Program.Main Configuration version: V1
- /// 08:26:30 Info Program.Main Processing: sequentiell
- ///
- public class Log : ILogger + /// + /// Example: + /// 08:26:30 Info Program.Main Startup + /// 08:26:30 Info Program.Main -------------------------------------------------- + /// 08:26:30 Info Program.Main Version: 0.0.0.1 + /// 08:26:30 Info Program.Main Arguments: -test + /// 08:26:30 Info Program.Main Configuration version: V1 + /// 08:26:30 Info Program.Main Processing: sequentiell + /// + public class Log : ILog { /// - /// Enumeration defines the different log levels. + /// The directory where the log is created. + /// + private string _path; + + /// + /// The thread that takes care of the cyclic writing in the log file. + /// + private Thread _workerThread; + + /// + /// Constant that determines the further of the separator rows. + /// + private const int _seperatorWidth = 260; + + /// + /// End worker thread lifecycle. /// - public enum Level { Info, Warning, FatalError, Error, Exception, Debug, Seperartor }; + private bool _done = false; /// - /// Enumerations of the log mode. + /// The width of the log entry output in the console. /// - public enum Mode { Off, Append, Override }; + private readonly int _width = 250; /// /// Returns or sets the encoding. @@ -81,7 +96,7 @@ public enum Mode { Off, Append, Override }; /// /// Returns the log mode. /// - public Mode LogMode { get; set; } + public LogMode LogMode { get; set; } /// /// The default instance of the logger. @@ -89,58 +104,29 @@ public enum Mode { Off, Append, Override }; public static Log Current { get; } = new Log(); /// - /// The directory where the log is created. - /// - private string _path; - - /// - /// The thread that takes care of the cyclic writing in the log file. - /// - private Thread _workerThread; - - /// - /// Constant that determines the further of the separator rows. + /// Set file name time patterns. /// - private const int _seperatorWidth = 260; + public string FilePattern { set; get; } /// - /// End worker thread lifecycle. + /// Time patternsspecifying log entries. /// - private bool _done = false; + public string TimePattern { set; get; } /// /// Unsaved entries queue. /// - private readonly Queue _queue = new Queue(); + private readonly Queue _queue = new(); /// - /// Returns the number of characters for log outputs in the console. - /// - private int Width - { - get - { - try - { - return Console.WindowWidth; - } - catch - { - } - - return 250; - } - } - - /// - /// Constructor + /// Initializes a new instance of the class. /// public Log() { Encoding = Encoding.UTF8; FilePattern = "yyyyMMdd"; TimePattern = "yyyMMddHHmmss"; - LogMode = Log.Mode.Append; + LogMode = LogMode.Append; } /// @@ -161,7 +147,7 @@ public void Begin(string path, string name) } // Delete existing log file when overwrite mode is active - if (LogMode == Mode.Override) + if (LogMode == LogMode.Override) { try { @@ -199,7 +185,7 @@ public void Begin(string path) public void Begin(SettingLogItem settings) { Filename = settings.Filename; - LogMode = (Mode)Enum.Parse(typeof(Mode), settings.Modus); + LogMode = Enum.Parse(settings.Modus); Encoding = Encoding.GetEncoding(settings.Encoding); TimePattern = settings.Timepattern; DebugMode = settings.Debug; @@ -215,7 +201,7 @@ public void Begin(SettingLogItem settings) /// Method/ function that wants to log. /// The line number. /// The source file. - protected virtual void Add(Level level, string message, [CallerMemberName] string instance = null, [CallerLineNumber] int? line = null, [CallerFilePath] string file = null) + protected virtual void Add(LogLevel level, string message, [CallerMemberName] string instance = null, [CallerLineNumber] int? line = null, [CallerFilePath] string file = null) { foreach (var l in message?.Split(Environment.NewLine.ToCharArray(), StringSplitOptions.RemoveEmptyEntries)) { @@ -224,19 +210,19 @@ protected virtual void Add(Level level, string message, [CallerMemberName] strin var item = new LogItem(level, instance, l, TimePattern); switch (level) { - case Level.Error: - case Level.FatalError: - case Level.Exception: + case LogLevel.Error: + case LogLevel.FatalError: + case LogLevel.Exception: Console.ForegroundColor = ConsoleColor.Red; break; - case Level.Warning: + case LogLevel.Warning: Console.ForegroundColor = ConsoleColor.Yellow; break; default: break; } - Console.WriteLine(item.ToString().Length > _seperatorWidth ? item.ToString().Substring(0, _seperatorWidth - 3) + "..." : item.ToString().PadRight(Width, ' ')); + Console.WriteLine(item.ToString().Length > _seperatorWidth ? string.Concat(item.ToString().AsSpan(0, _seperatorWidth - 3), "...") : item.ToString().PadRight(_width, ' ')); Console.ResetColor(); _queue.Enqueue(item); @@ -258,7 +244,7 @@ public void Seperator() /// The separator. public void Seperator(char sepChar) { - Add(Level.Seperartor, "".PadRight(_seperatorWidth, sepChar)); + Add(LogLevel.Seperartor, "".PadRight(_seperatorWidth, sepChar)); } /// @@ -273,7 +259,7 @@ public void Info(string message, [CallerMemberName] string instance = null, [Cal var methodInfo = new StackTrace().GetFrame(1).GetMethod(); var className = methodInfo.ReflectedType.Name; - Add(Level.Info, message, $"{className}.{instance}", line, file); + Add(LogLevel.Info, message, $"{className}.{instance}", line, file); } /// @@ -289,7 +275,7 @@ public void Info(string message, [CallerMemberName] string instance = null, [Cal var methodInfo = new StackTrace().GetFrame(1).GetMethod(); var className = methodInfo.ReflectedType.Name; - Add(Level.Info, string.Format(message, args), $"{className}.{instance}", line, file); + Add(LogLevel.Info, string.Format(message, args), $"{className}.{instance}", line, file); } /// @@ -304,7 +290,7 @@ public void Warning(string message, [CallerMemberName] string instance = null, [ var methodInfo = new StackTrace().GetFrame(1).GetMethod(); var className = methodInfo.ReflectedType.Name; - Add(Level.Warning, message, $"{className}.{instance}", line, file); + Add(LogLevel.Warning, message, $"{className}.{instance}", line, file); WarningCount++; } @@ -322,7 +308,7 @@ public void Warning(string message, [CallerMemberName] string instance = null, [ var methodInfo = new StackTrace().GetFrame(1).GetMethod(); var className = methodInfo.ReflectedType.Name; - Add(Level.Warning, string.Format(message, args), $"{className}.{instance}", line, file); + Add(LogLevel.Warning, string.Format(message, args), $"{className}.{instance}", line, file); WarningCount++; } @@ -339,7 +325,7 @@ public void Error(string message, [CallerMemberName] string instance = null, [Ca var methodInfo = new StackTrace().GetFrame(1).GetMethod(); var className = methodInfo.ReflectedType.Name; - Add(Level.Error, message, $"{className}.{instance}", line, file); + Add(LogLevel.Error, message, $"{className}.{instance}", line, file); ErrorCount++; } @@ -357,7 +343,7 @@ public void Error(string message, [CallerMemberName] string instance = null, [Ca var methodInfo = new StackTrace().GetFrame(1).GetMethod(); var className = methodInfo.ReflectedType.Name; - Add(Level.Error, string.Format(message, args), $"{className}.{instance}", line, file); + Add(LogLevel.Error, string.Format(message, args), $"{className}.{instance}", line, file); ErrorCount++; } @@ -374,7 +360,7 @@ public void FatalError(string message, [CallerMemberName] string instance = null var methodInfo = new StackTrace().GetFrame(1).GetMethod(); var className = methodInfo.ReflectedType.Name; - Add(Level.FatalError, message, $"{className}.{instance}", line, file); + Add(LogLevel.FatalError, message, $"{className}.{instance}", line, file); ErrorCount++; } @@ -392,7 +378,7 @@ public void FatalError(string message, [CallerMemberName] string instance = null var methodInfo = new StackTrace().GetFrame(1).GetMethod(); var className = methodInfo.ReflectedType.Name; - Add(Level.FatalError, string.Format(message, args), $"{className}.{instance}", line, file); + Add(LogLevel.FatalError, string.Format(message, args), $"{className}.{instance}", line, file); ErrorCount++; } @@ -411,8 +397,8 @@ public void Exception(Exception ex, [CallerMemberName] string instance = null, [ lock (_queue) { - Add(Level.Exception, ex?.Message.Trim(), $"{className}.{instance}", line, file); - Add(Level.Exception, ex?.StackTrace != null ? ex?.StackTrace.Trim() : ex?.Message.Trim(), $"{className}.{instance}", line, file); + Add(LogLevel.Exception, ex?.Message.Trim(), $"{className}.{instance}", line, file); + Add(LogLevel.Exception, ex?.StackTrace != null ? ex?.StackTrace.Trim() : ex?.Message.Trim(), $"{className}.{instance}", line, file); ExceptionCount++; ErrorCount++; @@ -433,7 +419,7 @@ public void Debug(string message, [CallerMemberName] string instance = null, [Ca if (DebugMode) { - Add(Level.Debug, message, $"{className}.{instance}", line, file); + Add(LogLevel.Debug, message, $"{className}.{instance}", line, file); } } @@ -452,7 +438,7 @@ public void Debug(string message, [CallerMemberName] string instance = null, [Ca if (DebugMode) { - Add(Level.Debug, string.Format(message, args), $"{className}.{instance}", line, file); + Add(LogLevel.Debug, string.Format(message, args), $"{className}.{instance}", line, file); } } @@ -495,7 +481,7 @@ public void Flush() } // protect file writing from concurrent access - if (list.Count > 0 && LogMode != Mode.Off) + if (list.Count > 0 && LogMode != LogMode.Off) { lock (_path) { @@ -538,9 +524,9 @@ private void ThreadProc() /// The entry to write. Can also be an object. /// The exception that applies to this entry. /// Function to create a string message of the state and exception parameters. - void ILogger.Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) + void ILogger.Log(Microsoft.Extensions.Logging.LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) { - if (logLevel == LogLevel.Error) + if (logLevel == Microsoft.Extensions.Logging.LogLevel.Error) { var message = exception?.Message ?? formatter.Invoke(state, exception); Error(message, "Kestrel", null, null); @@ -553,7 +539,7 @@ void ILogger.Log(LogLevel logLevel, EventId eventId, TState state, Excep /// /// Level to be checked. /// True in the enabled state, false otherwise. - public bool IsEnabled(LogLevel logLevel) + public bool IsEnabled(Microsoft.Extensions.Logging.LogLevel logLevel) { return true; } @@ -568,15 +554,5 @@ public IDisposable BeginScope(TState state) { return null; } - - /// - /// Set file name time patterns. - /// - public string FilePattern { set; get; } - - /// - /// Time patternsspecifying log entries. - /// - public string TimePattern { set; get; } } } diff --git a/src/WebExpress.WebCore/LogFactory.cs b/src/WebExpress.WebCore/WebLog/LogFactory.cs similarity index 82% rename from src/WebExpress.WebCore/LogFactory.cs rename to src/WebExpress.WebCore/WebLog/LogFactory.cs index abfafc3..87507af 100644 --- a/src/WebExpress.WebCore/LogFactory.cs +++ b/src/WebExpress.WebCore/WebLog/LogFactory.cs @@ -1,7 +1,11 @@ using Microsoft.Extensions.Logging; +using System; -namespace WebExpress.WebCore +namespace WebExpress.WebCore.WebLog { + /// + /// Provides a factory for creating loggers and adding logger providers. + /// public class LogFactory : ILoggerFactory, ILoggerProvider { /// @@ -10,7 +14,6 @@ public class LogFactory : ILoggerFactory, ILoggerProvider /// The ILoggerProvider. public void AddProvider(ILoggerProvider provider) { - } /// @@ -30,7 +33,7 @@ public ILogger CreateLogger(string categoryName) /// public void Dispose() { - + GC.SuppressFinalize(this); } } } diff --git a/src/WebExpress.WebCore/LogFrame.cs b/src/WebExpress.WebCore/WebLog/LogFrame.cs similarity index 93% rename from src/WebExpress.WebCore/LogFrame.cs rename to src/WebExpress.WebCore/WebLog/LogFrame.cs index 8ea8397..d564b12 100644 --- a/src/WebExpress.WebCore/LogFrame.cs +++ b/src/WebExpress.WebCore/WebLog/LogFrame.cs @@ -1,7 +1,7 @@ using System; using System.Runtime.CompilerServices; -namespace WebExpress.WebCore +namespace WebExpress.WebCore.WebLog { /// /// Creates a frame of log entries. @@ -34,7 +34,7 @@ public class LogFrame : IDisposable protected Log Log { get; set; } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The log entry. /// The name. @@ -54,7 +54,7 @@ public LogFrame(Log log, string name, string additionalHeading = null, [CallerMe } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The name. /// The additional heading or zero. @@ -69,12 +69,11 @@ public LogFrame(string name, string additionalHeading = null, [CallerMemberName] /// /// Release unmanaged resources that were reserved during initialization. /// - /// The input data. - /// The output data. public virtual void Dispose() { Log.Info("".PadRight(80, '='), Instance, Line, File); Log.Info(Status, Instance, Line, File); + GC.SuppressFinalize(this); } } } diff --git a/src/WebExpress.WebCore/LogFrameSimple.cs b/src/WebExpress.WebCore/WebLog/LogFrameSimple.cs similarity index 79% rename from src/WebExpress.WebCore/LogFrameSimple.cs rename to src/WebExpress.WebCore/WebLog/LogFrameSimple.cs index 76a4028..2cca5d8 100644 --- a/src/WebExpress.WebCore/LogFrameSimple.cs +++ b/src/WebExpress.WebCore/WebLog/LogFrameSimple.cs @@ -1,7 +1,7 @@ using System; using System.Runtime.CompilerServices; -namespace WebExpress.WebCore +namespace WebExpress.WebCore.WebLog { /// /// Creates a frame of log entries. @@ -26,16 +26,16 @@ public class LogFrameSimple : IDisposable /// /// The log entry. /// - protected Log Log { get; set; } + protected ILog Log { get; set; } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The log entry. /// Method that wants to log. /// The line number. /// The source file. - public LogFrameSimple(Log log, [CallerMemberName] string instance = null, [CallerLineNumber] int? line = null, [CallerFilePath] string file = null) + public LogFrameSimple(ILog log, [CallerMemberName] string instance = null, [CallerLineNumber] int? line = null, [CallerFilePath] string file = null) { Instance = instance; Log = log; @@ -48,11 +48,10 @@ public LogFrameSimple(Log log, [CallerMemberName] string instance = null, [Calle /// /// Release unmanaged resources that were reserved during initialization. /// - /// The input data. - /// The output data. public virtual void Dispose() { Log.Info("".PadRight(80, '<'), Instance, Line, File); + GC.SuppressFinalize(this); } } } diff --git a/src/WebExpress.WebCore/LogItem.cs b/src/WebExpress.WebCore/WebLog/LogItem.cs similarity index 76% rename from src/WebExpress.WebCore/LogItem.cs rename to src/WebExpress.WebCore/WebLog/LogItem.cs index d92b38d..2891a74 100644 --- a/src/WebExpress.WebCore/LogItem.cs +++ b/src/WebExpress.WebCore/WebLog/LogItem.cs @@ -1,7 +1,6 @@ using System; -using static WebExpress.WebCore.Log; -namespace WebExpress.WebCore +namespace WebExpress.WebCore.WebLog { /// /// Log entry @@ -11,7 +10,7 @@ internal class LogItem /// /// Level of the entry. /// - private readonly Level m_level; + private readonly LogLevel m_level; /// /// The instance (location). @@ -29,12 +28,13 @@ internal class LogItem private readonly DateTime m_timestamp; /// - /// Constructor + /// Initializes a new instance of the class. /// - /// The level. - /// The modul/funktion. + /// The level of the log entry. + /// The module or function where the log entry originated. /// The log message. - public LogItem(Level level, string instance, string message, string timePattern) + /// The time pattern used for formatting the timestamp. + public LogItem(LogLevel level, string instance, string message, string timePattern) { m_level = level; m_instance = instance; @@ -49,9 +49,9 @@ public LogItem(Level level, string instance, string message, string timePattern) /// The log entry as a string public override string ToString() { - if (m_level != Level.Seperartor) + if (m_level != LogLevel.Seperartor) { - return m_timestamp.ToString(TimePattern) + " " + m_level.ToString().PadRight(9, ' ') + " " + m_instance.PadRight(19, ' ').Substring(0, 19) + " " + m_message; + return m_timestamp.ToString(TimePattern) + " " + m_level.ToString().PadRight(9, ' ') + " " + m_instance.PadRight(19, ' ')[..19] + " " + m_message; } else { @@ -62,7 +62,7 @@ public override string ToString() /// /// Returns the level of the entry. /// - public Level Level => m_level; + public LogLevel Level => m_level; /// /// Returns the instance (location). diff --git a/src/WebExpress.WebCore/WebLog/LogLevel.cs b/src/WebExpress.WebCore/WebLog/LogLevel.cs new file mode 100644 index 0000000..3406197 --- /dev/null +++ b/src/WebExpress.WebCore/WebLog/LogLevel.cs @@ -0,0 +1,43 @@ +namespace WebExpress.WebCore.WebLog +{ + /// + /// Enumeration defines the different log levels. + /// + public enum LogLevel + { + /// + /// An informational message. This is generally used for informational messages that display the progress of the application at coarse-grained level. + /// + Info, + + /// + /// A warning message. This is typically for situations that are not necessarily errors, but that may need attention during debugging or tuning. + /// + Warning, + + /// + /// A fatal error message. This is a very severe error event that will presumably lead the application to abort. + /// + FatalError, + + /// + /// An error message. This is a message indicating that an operation could not be completed. + /// + Error, + + /// + /// An exception message. This is a message indicating that an exception was thrown by an operation. + /// + Exception, + + /// + /// A debug message. This is a message containing low-level information for developers and system administrators for debugging purposes. + /// + Debug, + + /// + /// A separator message. This is used to separate groups of messages in the logging output. + /// + Seperartor + } +} diff --git a/src/WebExpress.WebCore/WebLog/LogManager.cs b/src/WebExpress.WebCore/WebLog/LogManager.cs new file mode 100644 index 0000000..7550f36 --- /dev/null +++ b/src/WebExpress.WebCore/WebLog/LogManager.cs @@ -0,0 +1,119 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using WebExpress.WebCore.Internationalization; +using WebExpress.WebCore.WebComponent; +using WebExpress.WebCore.WebPlugin; + +namespace WebExpress.WebCore.WebLog +{ + /// + /// Manages logging operations and integrates with the system components. + /// + public class LogManager : ILogManager, ISystemComponent + { + private readonly IComponentHub _componentHub; + private readonly IHttpServerContext _httpServerContext; + + /// + /// An event that fires when a log is added. + /// + public event EventHandler AddLog; + + /// + /// An event that fires when a log is removed. + /// + + public event EventHandler RemoveLog; + + /// + /// Returns the default log. + /// + public ILog DefaultLog => _httpServerContext.Log; + + /// + /// Initializes a new instance of the class. + /// + /// The component hub. + /// The reference to the context of the host. + [SuppressMessage("CodeQuality", "IDE0051:Remove unused private members", Justification = "Used via Reflection.")] + private LogManager(IComponentHub componentHub, IHttpServerContext httpServerContext) + { + _componentHub = componentHub; + _httpServerContext = httpServerContext; + + _componentHub.PluginManager.AddPlugin += OnAddPlugin; + _componentHub.PluginManager.RemovePlugin += OnRemovePlugin; + + _httpServerContext.Log.Debug + ( + I18N.Translate("webexpress.webcore:logmanager.initialization") + ); + } + + /// + /// Discovers and registers logs from the specified plugin. + /// + /// A context of a plugin whose logs are to be registered. + private void Register(IPluginContext pluginContext) + { + + } + + /// + /// Removes all logs associated with the specified plugin context. + /// + /// The context of the plugin that contains the log to remove. + public void Remove(IPluginContext pluginContext) + { + } + + /// + /// Raises the AddLog event. + /// + /// The page context. + private void OnAddLog(IPluginContext resourceContext) + { + AddLog?.Invoke(this, resourceContext); + } + + /// + /// Raises the RemoveLog event. + /// + /// The page context. + private void OnRemoveLog(IPluginContext pluginContext) + { + RemoveLog?.Invoke(this, pluginContext); + } + + /// + /// Raises the event when an plugin is added. + /// + /// The source of the event. + /// The context of the plugin being added. + private void OnAddPlugin(object sender, IPluginContext e) + { + Register(e); + } + + /// + /// Raises the event when a plugin is removed. + /// + /// The source of the event. + /// The context of the plugin being removed. + private void OnRemovePlugin(object sender, IPluginContext e) + { + Remove(e); + } + + /// + /// Release of unmanaged resources reserved during use. + /// + public void Dispose() + { + _componentHub.PluginManager.AddPlugin -= OnAddPlugin; + _componentHub.PluginManager.RemovePlugin -= OnRemovePlugin; + + GC.SuppressFinalize(this); + } + } +} diff --git a/src/WebExpress.WebCore/WebLog/LogMode.cs b/src/WebExpress.WebCore/WebLog/LogMode.cs new file mode 100644 index 0000000..1b466a6 --- /dev/null +++ b/src/WebExpress.WebCore/WebLog/LogMode.cs @@ -0,0 +1,23 @@ +namespace WebExpress.WebCore.WebLog +{ + /// + /// Enumerations of the log mode. + /// + public enum LogMode + { + /// + /// Logging is turned off. + /// + Off, + + /// + /// Logs are appended to any existing logs. + /// + Append, + + /// + /// Any existing logs are overridden. + /// + Override + } +} diff --git a/src/WebExpress.WebCore/WebMessage/HttpContext.cs b/src/WebExpress.WebCore/WebMessage/HttpContext.cs index e885161..b76dbf9 100644 --- a/src/WebExpress.WebCore/WebMessage/HttpContext.cs +++ b/src/WebExpress.WebCore/WebMessage/HttpContext.cs @@ -1,17 +1,19 @@ using Microsoft.AspNetCore.Http.Features; using System; -using System.Linq; using System.Net; using System.Text; namespace WebExpress.WebCore.WebMessage { + /// + /// Represents the context of an HTTP request and response. + /// public class HttpContext { /// /// The context of the web server. /// - public IHttpServerContext ServerContext { get; protected set; } + public IHttpServerContext HttpServerContext { get; protected set; } /// /// Returns or sets the id. @@ -49,19 +51,18 @@ public class HttpContext public Uri Uri { get; internal set; } /// - /// Constructor + /// Initializes a new instance of the class. /// internal HttpContext() { - } /// - /// Constructor + /// Initializes a new instance of the class. /// /// Initial set of features. - /// The context of the Web server. - public HttpContext(IFeatureCollection contextFeatures, IHttpServerContext serverContext) + /// The context of the Web server. + public HttpContext(IFeatureCollection contextFeatures, IHttpServerContext httpServerContext) { var connectionFeature = contextFeatures.Get(); var requestFeature = contextFeatures.Get(); @@ -73,10 +74,10 @@ public HttpContext(IFeatureCollection contextFeatures, IHttpServerContext server LocalEndPoint = new IPEndPoint(connectionFeature.LocalIpAddress, connectionFeature.LocalPort); RemoteEndPoint = new IPEndPoint(connectionFeature.RemoteIpAddress, connectionFeature.RemotePort); - Encoding = requestFeature.Headers.ContentEncoding.Any() ? Encoding.GetEncoding(requestFeature.Headers.ContentEncoding) : Encoding.Default; + Encoding = requestFeature.Headers.ContentEncoding.Count != 0 ? Encoding.GetEncoding(requestFeature.Headers.ContentEncoding) : Encoding.Default; Uri = new Uri(baseUri, requestFeature.RawTarget); - Request = new Request(contextFeatures, serverContext, header); + Request = new Request(contextFeatures, header, httpServerContext); } } } diff --git a/src/WebExpress.WebCore/WebMessage/HttpExceptionContext.cs b/src/WebExpress.WebCore/WebMessage/HttpExceptionContext.cs index f7c11f8..e4aa046 100644 --- a/src/WebExpress.WebCore/WebMessage/HttpExceptionContext.cs +++ b/src/WebExpress.WebCore/WebMessage/HttpExceptionContext.cs @@ -4,6 +4,9 @@ namespace WebExpress.WebCore.WebMessage { + /// + /// Represents the context for an HTTP exception, inheriting from . + /// public class HttpExceptionContext : HttpContext { /// @@ -12,14 +15,14 @@ public class HttpExceptionContext : HttpContext public Exception Exception { get; private set; } /// - /// Constructor + /// Initializes a new instance of the class. /// /// An exception that prevented the creation of the context. /// Initial set of features. public HttpExceptionContext(Exception exception, IFeatureCollection contextFeatures) { var connectionFeature = contextFeatures.Get(); - var requestFeature = contextFeatures.Get(); + //var requestFeature = contextFeatures.Get(); Features = contextFeatures; Id = connectionFeature.ConnectionId; diff --git a/src/WebExpress.WebCore/WebMessage/Parameter.cs b/src/WebExpress.WebCore/WebMessage/Parameter.cs index 6dd5a35..8f1d4fd 100644 --- a/src/WebExpress.WebCore/WebMessage/Parameter.cs +++ b/src/WebExpress.WebCore/WebMessage/Parameter.cs @@ -4,6 +4,9 @@ namespace WebExpress.WebCore.WebMessage { + /// + /// Represents a parameter with a key, value, and scope. + /// public class Parameter { /// @@ -12,24 +15,24 @@ public class Parameter public ParameterScope Scope { get; private set; } /// - /// The key. + /// Returns the key of the parameter. /// public string Key { get; private set; } /// - /// The value. + /// Returns the value of the parameter. /// public string Value { get; internal set; } /// - /// Constructor + /// Initializes a new instance of the class. /// public Parameter() { } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The key. /// The value. @@ -48,7 +51,7 @@ public Parameter(string key, Guid value, ParameterScope scope) } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The key. /// The value. @@ -67,7 +70,7 @@ public Parameter(string key, string value, ParameterScope scope) } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The key. /// The value. @@ -80,7 +83,7 @@ public Parameter(string key, int value, ParameterScope scope) } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The key. /// The value. @@ -99,17 +102,17 @@ public Parameter(string key, char value, ParameterScope scope) /// The parameter list. public static List Create(params Parameter[] param) { - return new List(param); + return [.. param]; } /// /// Returns the key. /// - /// The type. + /// The type. /// The key. - public static string GetKey() where T : Parameter + public static string GetKey() where TParameter : Parameter { - return (Activator.CreateInstance(typeof(T)) as T)?.Key; + return Activator.CreateInstance()?.Key; } /// diff --git a/src/WebExpress.WebCore/WebMessage/ParameterDictionary.cs b/src/WebExpress.WebCore/WebMessage/ParameterDictionary.cs index b4ba297..cb515fe 100644 --- a/src/WebExpress.WebCore/WebMessage/ParameterDictionary.cs +++ b/src/WebExpress.WebCore/WebMessage/ParameterDictionary.cs @@ -1,11 +1,11 @@ -using System.Collections.Generic; +using System.Collections.Concurrent; namespace WebExpress.WebCore.WebMessage { /// /// Management of parameters. /// - public class ParameterDictionary : Dictionary + public class ParameterDictionary : ConcurrentDictionary { } } diff --git a/src/WebExpress.WebCore/WebMessage/ParameterFile.cs b/src/WebExpress.WebCore/WebMessage/ParameterFile.cs index 41bb840..492396b 100644 --- a/src/WebExpress.WebCore/WebMessage/ParameterFile.cs +++ b/src/WebExpress.WebCore/WebMessage/ParameterFile.cs @@ -1,5 +1,8 @@ namespace WebExpress.WebCore.WebMessage { + /// + /// Represents a file parameter with content type and data. + /// public class ParameterFile : Parameter { /// @@ -14,14 +17,14 @@ public class ParameterFile : Parameter /// - /// Constructor + /// Initializes a new instance of the class. /// public ParameterFile() { } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The key. /// The value. @@ -32,7 +35,7 @@ public ParameterFile(string key, string value, ParameterScope scope) } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The key. /// The value. @@ -43,7 +46,7 @@ public ParameterFile(string key, int value, ParameterScope scope) } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The key. /// The value. diff --git a/src/WebExpress.WebCore/WebMessage/Request.cs b/src/WebExpress.WebCore/WebMessage/Request.cs index 9e2e08f..f0a706d 100644 --- a/src/WebExpress.WebCore/WebMessage/Request.cs +++ b/src/WebExpress.WebCore/WebMessage/Request.cs @@ -8,9 +8,8 @@ using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; -using WebExpress.WebCore.WebComponent; using WebExpress.WebCore.WebHtml; -using WebExpress.WebCore.WebSession; +using WebExpress.WebCore.WebSession.Model; using WebExpress.WebCore.WebUri; namespace WebExpress.WebCore.WebMessage @@ -24,7 +23,7 @@ public class Request /// /// The context of the web server. /// - public IHttpServerContext ServerContext { get; protected set; } + public IHttpServerContext HttpServerContext { get; protected set; } /// /// Returns the request method (e.g. POST). @@ -34,12 +33,12 @@ public class Request /// /// Returns the uri. /// - public UriResource Uri { get; internal set; } + public UriEndpoint Uri { get; internal set; } /// /// Returns the parameters. /// - private ParameterDictionary Param { get; } = new ParameterDictionary(); + private ParameterDictionary Param { get; } = []; /// /// Returns the session. @@ -66,31 +65,11 @@ public class Request /// public EndPoint RemoteEndPoint { get; private set; } - /// - /// Returns a boolean value that indicates whether the client sending this request is authenticated. - /// - //public bool IsAuthenticated { get; private set; } //=> RawRequuest.IsAuthenticated; - - /// - /// Returns a boolean value that indicates whether the request was sent from the local computer. - /// - //public bool IsLocal { get; private set; } //=> RawRequuest.IsLocal; - /// /// Returns a boolean value that indicates whether the tcp connection used to send the request uses the secure sockets layer (ssl) protocol. /// public bool IsSecureConnection { get; private set; } - /// - /// Returns a boolean value indicating whether the tcp connection was a web socket request. - /// - //public bool IsWebSocketRequest { get; private set; } // => RawRequuest.IsWebSocketRequest; - - /// - /// Returns a boolean value that indicates whether the client is requesting a persistent connection. - /// - //public bool KeepAlive { get; private set; } //=> RawRequuest.KeepAlive; - /// /// Returns the shema. This can be http or https. /// @@ -118,7 +97,7 @@ public CultureInfo Culture } catch { - return ServerContext.Culture ?? CultureInfo.CurrentCulture; + return HttpServerContext.Culture ?? CultureInfo.CurrentCulture; } } } @@ -129,19 +108,19 @@ public CultureInfo Culture public byte[] Content { get; private set; } /// - /// Constructor + /// Initializes a new instance of the class. /// /// Initial set of features. - /// The context of the web server. /// The header. - internal Request(IFeatureCollection contextFeatures, IHttpServerContext serverContext, RequestHeaderFields header) + /// The context of the web server. + internal Request(IFeatureCollection contextFeatures, RequestHeaderFields header, IHttpServerContext httpServerContext) { var connectionFeature = contextFeatures.Get(); var requestFeature = contextFeatures.Get(); var requestIdentifierFeature = contextFeatures.Get(); //var sessionFeature = contextFeatures.Get(); - ServerContext = serverContext; + HttpServerContext = httpServerContext; RequestTraceIdentifier = requestIdentifierFeature.TraceIdentifier; Protocoll = requestFeature.Protocol; @@ -172,7 +151,7 @@ internal Request(IFeatureCollection contextFeatures, IHttpServerContext serverCo LocalEndPoint = new IPEndPoint(connectionFeature.LocalIpAddress, connectionFeature.LocalPort); RemoteEndPoint = new IPEndPoint(connectionFeature.RemoteIpAddress, connectionFeature.RemotePort); - Uri = new UriResource + Uri = new UriEndpoint ( Scheme, new UriAuthority() @@ -250,7 +229,6 @@ private void ParseRequestParams() } var contentType = Header.ContentType?.Split(';'); - //var contentStr = Encoding.UTF8.GetString(Content); switch (TypeEnctypeExtensions.Convert(contentType.FirstOrDefault())) { @@ -260,7 +238,7 @@ private void ParseRequestParams() var boundaryValue = "--" + boundary?.Split('=').Skip(1)?.FirstOrDefault(); var offset = 0; int pos = 0; - var dispositions = new List>(); // Item1=Position, Item2=Länge + var dispositions = new List>(); // Item1=position, Item2=size // determine dispositions for (var i = 0; i < Content.Length; i++) @@ -451,9 +429,9 @@ private void ParseRequestParams() /// private void ParseSessionParams() { - Session = ComponentManager.SessionManager.GetSession(this); + Session = WebEx.ComponentHub.SessionManager?.GetSession(this); - var property = Session.GetProperty(); + var property = Session?.GetProperty(); if (property != null && property.Params != null) { foreach (var param in property.Params) @@ -481,13 +459,11 @@ public void AddParameter(IEnumerable param) /// The parameter. public void AddParameter(Parameter param) { - if (!Param.ContainsKey(param.Key.ToLower())) - { - Param.Add(param.Key.ToLower(), param); - } - else + var key = param.Key.ToLower(); + + if (!Param.TryAdd(key, param)) { - Param[param.Key.ToLower()] = param; + Param[key] = param; } } diff --git a/src/WebExpress.WebCore/WebMessage/RequestAuthorization.cs b/src/WebExpress.WebCore/WebMessage/RequestAuthorization.cs index 1983f03..428f21d 100644 --- a/src/WebExpress.WebCore/WebMessage/RequestAuthorization.cs +++ b/src/WebExpress.WebCore/WebMessage/RequestAuthorization.cs @@ -1,11 +1,20 @@ using System; -using System.Linq; using System.Text.RegularExpressions; namespace WebExpress.WebCore.WebMessage { - public class RequestAuthorization + /// + /// Represents an authorization request containing type, identification, and password. + /// + public partial class RequestAuthorization { + /// + /// Returns a regular expression to match the authorization header. + /// + /// A object for matching authorization headers. + [GeneratedRegex("^(.*) (.*)$")] + private static partial Regex AuthorizationRegex(); + /// /// Returns or sets the type.e (Basic bei WWW-Authenticate: Basic realm="RealmName") /// @@ -33,7 +42,7 @@ public static RequestAuthorization Parse(string str) return null; } - var m = Regex.Match(str, "^(.*) (.*)$"); + var m = AuthorizationRegex().Match(str); var type = "Basic"; var user = ""; var password = ""; @@ -47,7 +56,7 @@ public static RequestAuthorization Parse(string str) var split = userPw.Split(':'); user = split[0]; - password = split.Count() > 0 ? split[1] : ""; + password = split.Length > 0 ? split[1] : ""; } return new RequestAuthorization() diff --git a/src/WebExpress.WebCore/WebMessage/RequestHeaderFields.cs b/src/WebExpress.WebCore/WebMessage/RequestHeaderFields.cs index 9d9c2e7..7c1f998 100644 --- a/src/WebExpress.WebCore/WebMessage/RequestHeaderFields.cs +++ b/src/WebExpress.WebCore/WebMessage/RequestHeaderFields.cs @@ -8,7 +8,7 @@ namespace WebExpress.WebCore.WebMessage { /// - /// see RFC 2616 + /// Represents the header fields of an HTTP request as defined in RFC 2616. /// public class RequestHeaderFields { @@ -50,7 +50,7 @@ public class RequestHeaderFields /// /// Returns the accepted media types. /// - public ICollection Accept { get; private set; } + public IEnumerable Accept { get; private set; } /// /// Returns the accepted encodings. @@ -70,7 +70,7 @@ public class RequestHeaderFields /// /// Returns the cookies. /// - public ICollection Cookies { get; } = new List(); + public IEnumerable Cookies { get; } = []; /// /// Returns the referer. The referer header echoes the absolute or partial address from @@ -80,7 +80,7 @@ public class RequestHeaderFields public string Referer { get; private set; } /// - /// Constructor + /// Initializes a new instance of the class. /// /// Initial set of features. internal RequestHeaderFields(IFeatureCollection contextFeatures) @@ -92,22 +92,26 @@ internal RequestHeaderFields(IFeatureCollection contextFeatures) ContentType = requestFeature.Headers.ContentType; ContentLength = requestFeature.Headers.ContentLength ?? 0; ContentLanguage = requestFeature.Headers.ContentLanguage; - ContentEncoding = requestFeature.Headers.ContentEncoding.Any() ? Encoding.GetEncoding(requestFeature.Headers.ContentEncoding) : Encoding.Default; + ContentEncoding = requestFeature.Headers.ContentEncoding.Count != 0 ? Encoding.GetEncoding(requestFeature.Headers.ContentEncoding) : Encoding.Default; Accept = requestFeature.Headers.Accept; AcceptEncoding = requestFeature.Headers.AcceptEncoding; AcceptLanguage = requestFeature.Headers.AcceptLanguage.SelectMany(x => x.Split(';', StringSplitOptions.RemoveEmptyEntries)); UserAgent = requestFeature.Headers.UserAgent; Referer = requestFeature.Headers.Referer; + var cookies = new List(); + foreach (var cookie in requestFeature.Headers.Cookie) { var split = cookie.Split('='); var key = split[0]; var value = split[1]; - Cookies.Add(new Cookie(key, value)); + cookies.Add(new Cookie(key, value)); } + Cookies = cookies; + Authorization = RequestAuthorization.Parse(requestFeature.Headers.Authorization); } } diff --git a/src/WebExpress.WebCore/WebMessage/RequestMethod.cs b/src/WebExpress.WebCore/WebMessage/RequestMethod.cs index 1f8abbd..9e55e71 100644 --- a/src/WebExpress.WebCore/WebMessage/RequestMethod.cs +++ b/src/WebExpress.WebCore/WebMessage/RequestMethod.cs @@ -1,23 +1,57 @@ namespace WebExpress.WebCore.WebMessage { + /// + /// Enumeration of HTTP request methods. + /// public enum RequestMethod { + /// + /// No request method specified. + /// NONE, + + /// + /// The GET method requests a representation of the specified resource. + /// GET, + + /// + /// The POST method submits an entity to the specified resource. + /// POST, + + /// + /// The PUT method replaces all current representations of the target resource with the request payload. + /// PUT, + + /// + /// The HEAD method asks for a response identical to a GET request, but without the response body. + /// HEAD, + + /// + /// The DELETE method deletes the specified resource. + /// DELETE, + + /// + /// The PATCH method applies partial modifications to a resource. + /// PATCH // RFC 5789 } + /// + /// Provides extension methods for the enumeration. + /// public static class RequestMethodExtensions { + /// - /// Umwandlung in eine CSS-Klasse + /// Converts the enumeration value to its string representation. /// - /// Das Layout, welches umgewandelt werden soll - /// Die zum Layout gehörende CSS-KLasse + /// The enumeration value. + /// A string representation of the enumeration value. public static string ToString(this RequestMethod layout) { return layout switch diff --git a/src/WebExpress.WebCore/WebMessage/Response.cs b/src/WebExpress.WebCore/WebMessage/Response.cs index 46f9f82..504238d 100644 --- a/src/WebExpress.WebCore/WebMessage/Response.cs +++ b/src/WebExpress.WebCore/WebMessage/Response.cs @@ -1,32 +1,35 @@ -namespace WebExpress.WebCore.WebMessage +using System.Reflection; +using WebExpress.WebCore.WebAttribute; + +namespace WebExpress.WebCore.WebMessage { /// - /// siehe RFC 2616 Tz. 6 + /// Represents a response according to RFC 2616 Section 6. /// - public class Response + public abstract class Response { /// - /// Setzt oder liefert die Optionen + /// Returns the response header fields. /// public ResponseHeaderFields Header { get; } = new ResponseHeaderFields(); /// - /// Setzt oder liefert den Content + /// Returns or sets the response content. /// public object Content { get; set; } /// - /// Liefert oder setzt den Statuscode + /// Returns the status code of the response. /// - public int Status { get; protected set; } + public int Status => GetType().GetCustomAttribute().StatusCode; /// - /// Liefert oder setzt den Statustext + /// Returns or sets the reason phrase of the response. /// public string Reason { get; protected set; } /// - /// Constructor + /// Initializes a new instance of the Response class. /// protected Response() { diff --git a/src/WebExpress.WebCore/WebMessage/ResponseBadRequest.cs b/src/WebExpress.WebCore/WebMessage/ResponseBadRequest.cs index f147e28..1032f9e 100644 --- a/src/WebExpress.WebCore/WebMessage/ResponseBadRequest.cs +++ b/src/WebExpress.WebCore/WebMessage/ResponseBadRequest.cs @@ -1,17 +1,29 @@ -namespace WebExpress.WebCore.WebMessage +using WebExpress.WebCore.WebAttribute; +using WebExpress.WebCore.WebStatusPage; + +namespace WebExpress.WebCore.WebMessage { /// - /// siehe RFC 2616 Tz. 6 + /// Represents a response for a bad request (HTTP 400). See RFC 2616 Section 6 /// + [StatusCode(400)] public class ResponseBadRequest : Response { /// - /// Constructor + /// Initializes a new instance of the class. /// public ResponseBadRequest() + : this(null) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The user defined status message or null. + public ResponseBadRequest(StatusMessage message) { - var content = "404404 - Bad Request"; - Status = 400; + var content = message?.Message ?? "404404 - Bad Request"; Reason = "Bad Request"; Header.ContentType = "text/html"; diff --git a/src/WebExpress.WebCore/WebMessage/ResponseForbidden.cs b/src/WebExpress.WebCore/WebMessage/ResponseForbidden.cs index 7e2ff3b..f47f220 100644 --- a/src/WebExpress.WebCore/WebMessage/ResponseForbidden.cs +++ b/src/WebExpress.WebCore/WebMessage/ResponseForbidden.cs @@ -1,17 +1,29 @@ -namespace WebExpress.WebCore.WebMessage +using WebExpress.WebCore.WebAttribute; +using WebExpress.WebCore.WebStatusPage; + +namespace WebExpress.WebCore.WebMessage { /// - /// siehe RFC 2616 Tz. 6 + /// Represents a response according to RFC 2616 Section 6. /// + [StatusCode(403)] public class ResponseForbidden : Response { /// - /// Constructor + /// Initializes a new instance of the class. /// public ResponseForbidden() + : this(null) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The user defined status message or null. + public ResponseForbidden(StatusMessage message) { - var content = "403403 - Forbidden"; - Status = 403; + var content = message?.Message ?? "403403 - Forbidden"; Reason = "Forbidden"; Header.ContentType = "text/html"; diff --git a/src/WebExpress.WebCore/WebMessage/ResponseHeaderFields.cs b/src/WebExpress.WebCore/WebMessage/ResponseHeaderFields.cs index f0f85ba..303ff8f 100644 --- a/src/WebExpress.WebCore/WebMessage/ResponseHeaderFields.cs +++ b/src/WebExpress.WebCore/WebMessage/ResponseHeaderFields.cs @@ -5,57 +5,57 @@ namespace WebExpress.WebCore.WebMessage { /// - /// siehe RFC 2616 + /// Represents the response header fields as per RFC 2616. /// public class ResponseHeaderFields { /// - /// Liefert oder setzt die Content-Länge + /// Returns or sets the content length. /// public int ContentLength { get; set; } /// - /// Liefert oder setzt den Content-Typ + /// Returns or sets the content type. /// public string ContentType { get; set; } /// - /// Liefert oder setzt die Sprache des Content + /// Returns or sets the content language. /// public string ContentLanguage { get; set; } /// - /// Liefert oder setzt die Direktiven für das Caching (siehe RFC 7234) + /// Returns or sets the cache control directives (see RFC 7234). /// public string CacheControl { get; set; } /// - /// ContentDisposition + /// Returns or sets the content disposition. /// public string ContentDisposition { get; set; } /// - /// Die Basic Authentication (Basisauthentifizierung) nach RFC 2617 + /// Returns or sets a value indicating whether basic authentication (as per RFC 2617) is required. /// public bool WWWAuthenticate { get; set; } /// - /// Location + /// Returns or sets the location. /// public string Location { get; set; } /// - /// Benutzerdefinierte Header + /// Returns the custom headers. /// - public Dictionary CustomHeader { get; private set; } + public IDictionary CustomHeader { get; private set; } /// - /// Liefert oder setzt die Cookies + /// Returns the cookies. /// - public CookieCollection Cookies { get; } = new CookieCollection(); + public CookieCollection Cookies { get; } = []; /// - /// Constructor + /// Initializes a new instance of the class. /// public ResponseHeaderFields() { @@ -65,10 +65,10 @@ public ResponseHeaderFields() } /// - /// Setzt ein benutzerdefinierten Header + /// Adds a custom header. /// - /// - /// + /// The header key. + /// The header value. public void AddCustomHeader(string key, string value) { if (!CustomHeader.ContainsKey(key)) @@ -82,9 +82,9 @@ public void AddCustomHeader(string key, string value) } /// - /// In Stringform umwandeln + /// Converts the response header fields to a string representation. /// - /// + /// A string representation of the response header fields. public override string ToString() { var sb = new StringBuilder(); diff --git a/src/WebExpress.WebCore/WebMessage/ResponseInternalServerError.cs b/src/WebExpress.WebCore/WebMessage/ResponseInternalServerError.cs index 3d469c9..a0c05e9 100644 --- a/src/WebExpress.WebCore/WebMessage/ResponseInternalServerError.cs +++ b/src/WebExpress.WebCore/WebMessage/ResponseInternalServerError.cs @@ -1,17 +1,29 @@ -namespace WebExpress.WebCore.WebMessage +using WebExpress.WebCore.WebAttribute; +using WebExpress.WebCore.WebStatusPage; + +namespace WebExpress.WebCore.WebMessage { /// - /// siehe RFC 2616 Tz. 6 + /// Represents a response for the HTTP 500 Internal Server Error status code according to RFC 2616 Section 6. /// + [StatusCode(500)] public class ResponseInternalServerError : Response { /// - /// Constructor + /// Initializes a new instance of the class. /// public ResponseInternalServerError() + : this(null) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The user defined status message or null. + public ResponseInternalServerError(StatusMessage message) { - var content = "404500 - Internal Server Error"; - Status = 500; + var content = message?.Message ?? "404500 - Internal Server Error"; Reason = "Internal Server Error"; Header.ContentType = "text/html"; diff --git a/src/WebExpress.WebCore/WebMessage/ResponseMovedPermanently.cs b/src/WebExpress.WebCore/WebMessage/ResponseMovedPermanently.cs new file mode 100644 index 0000000..a5ac961 --- /dev/null +++ b/src/WebExpress.WebCore/WebMessage/ResponseMovedPermanently.cs @@ -0,0 +1,35 @@ +using WebExpress.WebCore.WebAttribute; +using WebExpress.WebCore.WebStatusPage; + +namespace WebExpress.WebCore.WebMessage +{ + /// + /// Represents a response for a resource moved permanently (301) according to RFC 2616 Section 6. + /// + [StatusCode(301)] + public class ResponseMovedPermanently : Response + { + /// + /// Initializes a new instance of the class. + /// + public ResponseMovedPermanently() + : this(null) + { + + } + + /// + /// Initializes a new instance of the class. + /// + /// The user defined status message or null. + public ResponseMovedPermanently(StatusMessage message) + { + var content = message?.Message ?? "404301 - Moved Permanently"; + Reason = "Moved Permanently"; + + Header.ContentType = "text/html"; + Header.ContentLength = content.Length; + Content = content; + } + } +} diff --git a/src/WebExpress.WebCore/WebMessage/ResponseNotFound.cs b/src/WebExpress.WebCore/WebMessage/ResponseNotFound.cs index 12b6011..43ba686 100644 --- a/src/WebExpress.WebCore/WebMessage/ResponseNotFound.cs +++ b/src/WebExpress.WebCore/WebMessage/ResponseNotFound.cs @@ -1,17 +1,30 @@ -namespace WebExpress.WebCore.WebMessage +using WebExpress.WebCore.WebAttribute; +using WebExpress.WebCore.WebStatusPage; + +namespace WebExpress.WebCore.WebMessage { /// - /// siehe RFC 2616 Tz. 6 + /// Represents a response for a resource not found (404) according to RFC 2616 Section 6. /// + [StatusCode(404)] public class ResponseNotFound : Response { /// - /// Constructor + /// Initializes a new instance of the class. /// public ResponseNotFound() + : this(null) + { + + } + + /// + /// Initializes a new instance of the class. + /// + /// The user defined status message or null. + public ResponseNotFound(StatusMessage message) { - var content = "404404 - Not Found"; - Status = 404; + var content = message?.Message ?? "404404 - Not Found"; Reason = "Not Found"; Header.ContentType = "text/html"; diff --git a/src/WebExpress.WebCore/WebMessage/ResponseOK.cs b/src/WebExpress.WebCore/WebMessage/ResponseOK.cs index 606ce6c..87d7a81 100644 --- a/src/WebExpress.WebCore/WebMessage/ResponseOK.cs +++ b/src/WebExpress.WebCore/WebMessage/ResponseOK.cs @@ -1,16 +1,18 @@ -namespace WebExpress.WebCore.WebMessage +using WebExpress.WebCore.WebAttribute; + +namespace WebExpress.WebCore.WebMessage { /// - /// siehe RFC 2616 Tz. 6 + /// Represents a successful response according to RFC 2616 Section 6. /// + [StatusCode(200)] public class ResponseOK : Response { /// - /// Constructor + /// Initializes a new instance of the class. /// public ResponseOK() { - Status = 200; Reason = "OK"; } } diff --git a/src/WebExpress.WebCore/WebMessage/ResponseRedirectPermanentlyMoved.cs b/src/WebExpress.WebCore/WebMessage/ResponseRedirectPermanentlyMoved.cs index 0fbfb81..2c57bf7 100644 --- a/src/WebExpress.WebCore/WebMessage/ResponseRedirectPermanentlyMoved.cs +++ b/src/WebExpress.WebCore/WebMessage/ResponseRedirectPermanentlyMoved.cs @@ -1,16 +1,18 @@ -namespace WebExpress.WebCore.WebMessage +using WebExpress.WebCore.WebAttribute; + +namespace WebExpress.WebCore.WebMessage { /// - /// siehe RFC 2616 Tz. 6 + /// Represents a response according to RFC 2616 Section 6. /// + [StatusCode(301)] public class ResponseRedirectPermanentlyMoved : Response { /// - /// Constructor + /// Initializes a new instance of the class. /// public ResponseRedirectPermanentlyMoved(string location) { - Status = 301; Reason = "permanently moved"; Header.Location = location; diff --git a/src/WebExpress.WebCore/WebMessage/ResponseRedirectTemporarilyMoved.cs b/src/WebExpress.WebCore/WebMessage/ResponseRedirectTemporarilyMoved.cs index a50c499..c5576ac 100644 --- a/src/WebExpress.WebCore/WebMessage/ResponseRedirectTemporarilyMoved.cs +++ b/src/WebExpress.WebCore/WebMessage/ResponseRedirectTemporarilyMoved.cs @@ -1,16 +1,18 @@ -namespace WebExpress.WebCore.WebMessage +using WebExpress.WebCore.WebAttribute; + +namespace WebExpress.WebCore.WebMessage { /// - /// siehe RFC 2616 Tz. 6 + /// Represents a response according to RFC 2616 Section 6. /// + [StatusCode(302)] public class ResponseRedirectTemporarilyMoved : Response { /// - /// Constructor + /// Initializes a new instance of the class. /// public ResponseRedirectTemporarilyMoved(string location) { - Status = 302; Reason = "temporarily moved"; //Content = ""; diff --git a/src/WebExpress.WebCore/WebMessage/ResponseUnauthorized.cs b/src/WebExpress.WebCore/WebMessage/ResponseUnauthorized.cs index b7e6c32..bef3d01 100644 --- a/src/WebExpress.WebCore/WebMessage/ResponseUnauthorized.cs +++ b/src/WebExpress.WebCore/WebMessage/ResponseUnauthorized.cs @@ -1,16 +1,29 @@ -namespace WebExpress.WebCore.WebMessage +using WebExpress.WebCore.WebAttribute; +using WebExpress.WebCore.WebStatusPage; + +namespace WebExpress.WebCore.WebMessage { /// - /// siehe RFC 2616 Tz. 6 + /// Represents a response indicating that the request requires user authentication. See RFC 2616 Section 6 /// + [StatusCode(401)] public class ResponseUnauthorized : Response { /// - /// Constructor + /// Initializes a new instance of the class. /// public ResponseUnauthorized() + : this(null) + { + + } + + /// + /// Initializes a new instance of the class. + /// + /// The user defined status message or null. + public ResponseUnauthorized(StatusMessage message) { - Status = 401; Reason = "OK"; Header.WWWAuthenticate = true; diff --git a/src/WebExpress.WebCore/WebModule/IModule.cs b/src/WebExpress.WebCore/WebModule/IModule.cs deleted file mode 100644 index ca30186..0000000 --- a/src/WebExpress.WebCore/WebModule/IModule.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; - -namespace WebExpress.WebCore.WebModule -{ - public interface IModule : IDisposable - { - /// - /// Initialization of the module. - /// - /// The context. - void Initialization(IModuleContext context); - - /// - /// Called when the module starts working. The call is concurrent. - /// - void Run(); - } -} diff --git a/src/WebExpress.WebCore/WebModule/IModuleContext.cs b/src/WebExpress.WebCore/WebModule/IModuleContext.cs deleted file mode 100644 index 654df92..0000000 --- a/src/WebExpress.WebCore/WebModule/IModuleContext.cs +++ /dev/null @@ -1,54 +0,0 @@ -using WebExpress.WebCore.WebApplication; -using WebExpress.WebCore.WebPlugin; -using WebExpress.WebCore.WebUri; - -namespace WebExpress.WebCore.WebModule -{ - public interface IModuleContext - { - /// - /// Returns the context of the associated plugin. - /// - IPluginContext PluginContext { get; } - - /// - /// Returns the associated application context. - /// - IApplicationContext ApplicationContext { get; } - - /// - /// Returns the modul id. - /// - string ModuleId { get; } - - /// - /// Returns the module name. - /// - string ModuleName { get; } - - /// - /// Returns the description. - /// - string Description { get; } - - /// - /// Returns the asset directory. - /// - string AssetPath { get; } - - /// - /// Returns the data directory. - /// - string DataPath { get; } - - /// - /// Returns the context path. - /// - UriResource ContextPath { get; } - - /// - /// Returns the icon uri. - /// - UriResource Icon { get; } - } -} diff --git a/src/WebExpress.WebCore/WebModule/ModuleContext.cs b/src/WebExpress.WebCore/WebModule/ModuleContext.cs deleted file mode 100644 index 155f2ea..0000000 --- a/src/WebExpress.WebCore/WebModule/ModuleContext.cs +++ /dev/null @@ -1,70 +0,0 @@ -using WebExpress.WebCore.WebApplication; -using WebExpress.WebCore.WebPlugin; -using WebExpress.WebCore.WebUri; - -namespace WebExpress.WebCore.WebModule -{ - public class ModuleContext : IModuleContext - { - /// - /// Returns the context of the associated plugin. - /// - public IPluginContext PluginContext { get; internal set; } - - /// - /// Returns the associated application context. - /// - public IApplicationContext ApplicationContext { get; internal set; } - - /// - /// Returns the modul id. - /// - public string ModuleId { get; internal set; } - - /// - /// Returns the module name. - /// - public string ModuleName { get; internal set; } - - /// - /// Returns the description. - /// - public string Description { get; internal set; } - - /// - /// Returns the asset directory. - /// - public string AssetPath { get; internal set; } - - /// - /// Returns the data directory. - /// - public string DataPath { get; internal set; } - - /// - /// Returns the context path. - /// - public UriResource ContextPath { get; internal set; } - - /// - /// Returns the icon uri. - /// - public UriResource Icon { get; internal set; } - - /// - /// Constructor - /// - public ModuleContext() - { - } - - /// - /// Conversion of the module context into its string representation. - /// - /// The string that uniquely represents the module. - public override string ToString() - { - return $"Module {ModuleId}"; - } - } -} diff --git a/src/WebExpress.WebCore/WebModule/ModuleDictionary.cs b/src/WebExpress.WebCore/WebModule/ModuleDictionary.cs deleted file mode 100644 index 33573e8..0000000 --- a/src/WebExpress.WebCore/WebModule/ModuleDictionary.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System.Collections.Generic; -using WebExpress.WebCore.WebPlugin; - -namespace WebExpress.WebCore.WebModule -{ - /// - /// Key = plugin context - /// Value = { Key = module id, Value = module item } - /// - internal class ModuleDictionary : Dictionary> - { - } -} diff --git a/src/WebExpress.WebCore/WebModule/ModuleItem.cs b/src/WebExpress.WebCore/WebModule/ModuleItem.cs deleted file mode 100644 index 3f3e1fb..0000000 --- a/src/WebExpress.WebCore/WebModule/ModuleItem.cs +++ /dev/null @@ -1,283 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Text.RegularExpressions; -using System.Threading.Tasks; -using WebExpress.WebCore.Internationalization; -using WebExpress.WebCore.WebApplication; -using WebExpress.WebCore.WebPlugin; -using WebExpress.WebCore.WebUri; - -namespace WebExpress.WebCore.WebModule -{ - /// - /// Represents a module entry in the module directory. - /// - internal class ModuleItem : IDisposable - { - /// - /// The assembly that contains the module. - /// - public Assembly Assembly { get; internal set; } - - /// - /// Returns the module class. - /// - public Type ModuleClass { get; internal set; } - - /// - /// Returns the context of the associated plugin. - /// - public IPluginContext PluginContext { get; internal set; } - - /// - /// Returns the associated application ids. - /// - public IEnumerable Applications { get; set; } - - /// - /// Returns the modul id. - /// - public string ModuleId { get; internal set; } - - /// - /// Returns the module name. - /// - public string ModuleName { get; internal set; } - - /// - /// Returns the description. - /// - public string Description { get; internal set; } - - /// - /// Returns the asset directory. - /// - public string AssetPath { get; internal set; } - - /// - /// Returns the data directory. - /// - public string DataPath { get; internal set; } - - /// - /// Returns the context path. - /// - public UriResource ContextPath { get; internal set; } - - /// - /// Returns the icon uri. - /// - public UriResource Icon { get; internal set; } - - /// - /// Returns the log to write status messages to the console and to a log file. - /// - public Log Log { get; internal set; } - - /// - /// Returns the directory where the module instances are listed. - /// - public IDictionary Dictionary { get; } - = new Dictionary(); - - /// - /// An event that fires when an module is added. - /// - public event EventHandler AddModule; - - /// - /// An event that fires when an module is removed. - /// - public event EventHandler RemoveModule; - - /// - /// Adds an application assignment - /// - /// The context of the application. - public void AddApplication(IApplicationContext applicationContext) - { - // only if no instance has been created yet - if (Dictionary.ContainsKey(applicationContext)) - { - return; - } - - // create context - var moduleContext = new ModuleContext() - { - PluginContext = PluginContext, - ApplicationContext = applicationContext, - ModuleId = ModuleId, - ModuleName = ModuleName, - Description = Description, - Icon = UriResource.Combine(applicationContext.ContextPath, ContextPath, Icon), - AssetPath = Path.Combine(applicationContext.AssetPath, AssetPath), - DataPath = Path.Combine(applicationContext.DataPath, DataPath), - ContextPath = UriResource.Combine(applicationContext.ContextPath, ContextPath) - }; - - if - ( - Applications.Contains("*") || - Applications.Contains(applicationContext.ApplicationId?.ToLower()) || - Applications.Where(x => Regex.Match(applicationContext.ApplicationId?.ToLower(), x).Success).Any() - ) - { - Dictionary.Add - ( - applicationContext, - new ModuleItemInstance() - { - ModuleContext = moduleContext - } - ); - - // raises the AddModule event - OnAddModule(moduleContext); - } - } - - /// - /// Remove an application assignment - /// - /// The context of the application. - public void DetachApplication(IApplicationContext applicationContext) - { - // not an instance has been created yet - if (!Dictionary.ContainsKey(applicationContext)) - { - return; - } - - var moduleItemValue = Dictionary[applicationContext]; - OnRemoveModule(moduleItemValue.ModuleContext); - - Dictionary.Remove(applicationContext); - } - - /// - /// Boots the module of each existing application if not yet booted. - /// - public void Boot() - { - foreach (var item in Dictionary.Values.Where(x => x.ModuleInstance == null)) - { - // create module - item.ModuleInstance = Activator.CreateInstance(ModuleClass) as IModule; - - // thread termination. - var token = item.CancellationTokenSource.Token; - - // initialize module - item.ModuleInstance.Initialization(item.ModuleContext); - - Log.Debug - ( - message: InternationalizationManager.I18N - ( - "webexpress:modulemanager.module.initialization", - item.ModuleContext.ApplicationContext.ApplicationId, - item.ModuleContext.PluginContext.PluginId - ) - ); - - // execute modules concurrently - Task.Run(() => - { - Log.Debug - ( - message: InternationalizationManager.I18N - ( - "webexpress:modulemanager.module.processing.start", - item.ModuleContext.ApplicationContext.ApplicationId, - item.ModuleContext.PluginContext.PluginId - ) - ); - - item.ModuleInstance.Run(); - - Log.Debug - ( - message: InternationalizationManager.I18N - ( - "webexpress:modulemanager.module.processing.end", - item.ModuleContext.ApplicationContext.ApplicationId, - item.ModuleContext.PluginContext.PluginId - ) - ); - - token.ThrowIfCancellationRequested(); - }, token); - } - } - - /// - /// Terminate modules of a plugin. - /// - public void ShutDown() - { - foreach (var item in Dictionary.Values) - { - item.CancellationTokenSource?.Cancel(); - } - } - - /// - /// Terminate modules of a application. - /// - /// The context of the application containing the modules. - public void ShutDown(IApplicationContext applicationContext) - { - if (Dictionary.ContainsKey(applicationContext) - && Dictionary[applicationContext].CancellationTokenSource != null) - { - Dictionary[applicationContext].CancellationTokenSource.Cancel(); - } - } - - /// - /// Raises the AddModule event. - /// - private void OnAddModule(IModuleContext moduleContext) - { - AddModule?.Invoke(this, moduleContext); - } - - /// - /// Raises the RemoveModule event. - /// - /// The module context. - private void OnRemoveModule(IModuleContext moduleContext) - { - RemoveModule?.Invoke(this, moduleContext); - } - - /// - /// Performs application-specific tasks related to sharing, returning, or resetting unmanaged resources. - /// - public void Dispose() - { - foreach (Delegate d in AddModule.GetInvocationList()) - { - AddModule -= (EventHandler)d; - } - - foreach (Delegate d in RemoveModule.GetInvocationList()) - { - RemoveModule -= (EventHandler)d; - } - } - - /// - /// Convert the module element to a string. - /// - /// The resource element in its string representation. - public override string ToString() - { - return $"Module '{ModuleId}'"; - } - } -} diff --git a/src/WebExpress.WebCore/WebModule/ModuleItemInstance.cs b/src/WebExpress.WebCore/WebModule/ModuleItemInstance.cs deleted file mode 100644 index 55f30cc..0000000 --- a/src/WebExpress.WebCore/WebModule/ModuleItemInstance.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System.Threading; - -namespace WebExpress.WebCore.WebModule -{ - public class ModuleItemInstance - { - /// - /// Returns the module context. - /// - public IModuleContext ModuleContext { get; internal set; } - - /// - /// Returns the module instance or null if not already created. - /// - public IModule ModuleInstance { get; internal set; } - - /// - /// Returns the cancel token or null if not already created. - /// - public CancellationTokenSource CancellationTokenSource { get; } = new CancellationTokenSource(); - } -} diff --git a/src/WebExpress.WebCore/WebModule/ModuleManager.cs b/src/WebExpress.WebCore/WebModule/ModuleManager.cs deleted file mode 100644 index ec4a6e1..0000000 --- a/src/WebExpress.WebCore/WebModule/ModuleManager.cs +++ /dev/null @@ -1,446 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using WebExpress.WebCore.Internationalization; -using WebExpress.WebCore.WebApplication; -using WebExpress.WebCore.WebAttribute; -using WebExpress.WebCore.WebComponent; -using WebExpress.WebCore.WebPlugin; -using WebExpress.WebCore.WebUri; - -namespace WebExpress.WebCore.WebModule -{ - /// - /// The module manager manages the WebExpress modules. - /// - public sealed class ModuleManager : IComponentPlugin, IExecutableElements, ISystemComponent - { - /// - /// An event that fires when an module is added. - /// - public event EventHandler AddModule; - - /// - /// An event that fires when an module is removed. - /// - public event EventHandler RemoveModule; - - /// - /// Returns or sets the reference to the context of the host. - /// - public IHttpServerContext HttpServerContext { get; private set; } - - /// - /// Returns the directory where the modules are listed. - /// - private ModuleDictionary Dictionary { get; } = new ModuleDictionary(); - - /// - /// Delivers all stored modules. - /// - public IEnumerable Modules => Dictionary.Values - .SelectMany(x => x.Values) - .SelectMany(x => x.Dictionary.Values) - .Select(x => x.ModuleContext); - - /// - /// Constructor - /// - internal ModuleManager() - { - ComponentManager.PluginManager.AddPlugin += (sender, pluginContext) => - { - Register(pluginContext); - }; - - ComponentManager.PluginManager.RemovePlugin += (sender, pluginContext) => - { - Remove(pluginContext); - }; - - ComponentManager.ApplicationManager.AddApplication += (sender, applicationContext) => - { - AssignToApplication(applicationContext); - }; - - ComponentManager.ApplicationManager.RemoveApplication += (sender, applicationContext) => - { - DetachFromApplication(applicationContext); - }; - } - - /// - /// Initialization - /// - /// The reference to the context of the host. - public void Initialization(IHttpServerContext context) - { - HttpServerContext = context; - - HttpServerContext.Log.Debug - ( - InternationalizationManager.I18N("webexpress:modulemanager.initialization") - ); - } - - /// - /// Discovers and registers modules from the specified plugin. - /// - /// A context of a plugin whose modules are to be registered. - public void Register(IPluginContext pluginContext) - { - if (Dictionary.ContainsKey(pluginContext)) - { - return; - } - - var assembly = pluginContext.Assembly; - - foreach (var type in assembly.GetExportedTypes().Where - ( - x => x.IsClass && - x.IsSealed && - x.IsPublic && - x.GetInterface(typeof(IModule).Name) != null - )) - { - var id = type.FullName?.ToLower(); - var name = type.Name; - var icon = string.Empty; - var description = string.Empty; - var contextPath = string.Empty; - var assetPath = string.Empty; - var dataPath = string.Empty; - var applicationIds = new List(); - - foreach (var customAttribute in type.CustomAttributes - .Where(x => x.AttributeType.GetInterfaces() - .Contains(typeof(IModuleAttribute)))) - { - if (customAttribute.AttributeType == typeof(NameAttribute)) - { - name = customAttribute.ConstructorArguments.FirstOrDefault().Value?.ToString(); - } - else if (customAttribute.AttributeType == typeof(IconAttribute)) - { - icon = customAttribute.ConstructorArguments.FirstOrDefault().Value?.ToString(); - } - else if (customAttribute.AttributeType == typeof(DescriptionAttribute)) - { - description = customAttribute.ConstructorArguments.FirstOrDefault().Value?.ToString(); - } - else if (customAttribute.AttributeType == typeof(ContextPathAttribute)) - { - contextPath = customAttribute.ConstructorArguments.FirstOrDefault().Value?.ToString(); - } - else if (customAttribute.AttributeType == typeof(AssetPathAttribute)) - { - assetPath = customAttribute.ConstructorArguments.FirstOrDefault().Value?.ToString(); - } - else if (customAttribute.AttributeType == typeof(DataPathAttribute)) - { - dataPath = customAttribute.ConstructorArguments.FirstOrDefault().Value?.ToString(); - } - else if (customAttribute.AttributeType == typeof(ApplicationAttribute)) - { - applicationIds.Add(customAttribute.ConstructorArguments.FirstOrDefault().Value?.ToString().Trim()); - } - else if (customAttribute.AttributeType.Name == typeof(ApplicationAttribute<>).Name && customAttribute.AttributeType.Namespace == typeof(ApplicationAttribute<>).Namespace) - { - applicationIds.Add(customAttribute.AttributeType.GenericTypeArguments.FirstOrDefault()?.FullName?.ToLower()); - } - } - - if (!applicationIds.Any()) - { - // no application specified - HttpServerContext.Log.Warning - ( - InternationalizationManager.I18N("webexpress:modulemanager.applicationless", id) - ); - } - - Dictionary.Add(pluginContext, new Dictionary()); - var item = Dictionary[pluginContext]; - - if (!item.ContainsKey(id)) - { - var moduleItem = new ModuleItem() - { - Assembly = assembly, - ModuleClass = type, - PluginContext = pluginContext, - Applications = applicationIds, - ModuleId = id, - ModuleName = name, - Description = description, - Icon = new UriResource(icon), - AssetPath = assetPath, - ContextPath = new UriResource(contextPath), - DataPath = dataPath, - Log = HttpServerContext.Log - }; - - moduleItem.AddModule += (s, e) => - { - OnAddModule(e); - }; - - moduleItem.RemoveModule += (s, e) => - { - OnRemoveModule(e); - }; - - item.Add(id, moduleItem); - - // assign the module to existing applications. - foreach (var applicationContext in ComponentManager.ApplicationManager.Applications) - { - AssignToApplication(applicationContext); - } - - HttpServerContext.Log.Debug - ( - InternationalizationManager.I18N - ( - "webexpress:modulemanager.register", - id, - string.Join(", ", applicationIds) - ) - ); - } - else - { - HttpServerContext.Log.Warning - ( - InternationalizationManager.I18N - ( - "webexpress:modulemanager.duplicate", - id, - string.Join(", ", applicationIds) - ) - ); - } - } - } - - /// - /// Discovers and registers modules from the specified plugin. - /// - /// A list with plugin contexts that contain the modules. - public void Register(IEnumerable pluginContexts) - { - foreach (var pluginContext in pluginContexts) - { - Register(pluginContext); - } - } - - /// - /// Assign existing modules to the application. - /// - /// The context of the application. - private void AssignToApplication(IApplicationContext applicationContext) - { - foreach (var moduleItem in Dictionary.Values.SelectMany(x => x.Values)) - { - if (moduleItem.Applications.Contains("*") - || moduleItem.Applications.Contains(applicationContext?.ApplicationId?.ToLower())) - { - moduleItem.AddApplication(applicationContext); - } - } - } - - /// - /// Remove an existing modules to the application. - /// - /// The context of the application. - private void DetachFromApplication(IApplicationContext applicationContext) - { - foreach (var moduleItem in Dictionary.Values.SelectMany(x => x.Values)) - { - if (moduleItem.Applications.Contains("*") - || moduleItem.Applications.Contains(applicationContext?.ApplicationId?.ToLower())) - { - moduleItem.DetachApplication(applicationContext); - } - } - } - - /// - /// Determines the module for a given application context and module id. - /// - /// The context of the application. - /// The modul id. - /// The context of the module or null. - public IModuleContext GetModule(IApplicationContext applicationContext, string moduleId) - { - var item = Dictionary.Values - .SelectMany(x => x.Values) - .Where(x => x.Dictionary.ContainsKey(applicationContext)) - .Select(x => x.Dictionary[applicationContext]) - .Where(x => x.ModuleContext.ModuleId.Equals(moduleId, StringComparison.OrdinalIgnoreCase)) - .Select(x => x.ModuleContext) - .FirstOrDefault(); - - return item; - } - - /// - /// Determines the module for a given application context and module id. - /// - /// The context of the application. - /// The module class. - /// The context of the module or null. - public IModuleContext GetModule(IApplicationContext applicationContext, Type moduleClass) - { - return GetModule(applicationContext, moduleClass.FullName.ToLower()); - } - - /// - /// Determines the module for a given plugin context and module id. - /// - /// The context of the plugin. - /// The modul id. - /// An enumeration of the module contexts for the given plugin and module id. - public IEnumerable GetModules(IPluginContext pluginContext, string moduleId) - { - return GetModuleItems(pluginContext) - .Where(x => x.ModuleId.Equals(moduleId, StringComparison.OrdinalIgnoreCase)) - .SelectMany(x => x.Dictionary.Values) - .Select(x => x.ModuleContext); - } - - /// - /// Determines the module for a given plugin context and module id. - /// - /// The context of the plugin. - /// The modul id. - /// An enumeration of the module contexts for the given plugin and module id. - public IEnumerable GetModules(IApplicationContext applicationContext) - { - return Dictionary.Values - .SelectMany(x => x.Values) - .Where(x => x.Dictionary.ContainsKey(applicationContext)) - .Select(x => x.Dictionary[applicationContext]) - .Select(x => x.ModuleContext); - } - - /// - /// Returns the modules for a given plugin. - /// - /// The context of the plugin. - /// An enumeration of the module contexts for the given plugin. - internal IEnumerable GetModuleItems(IPluginContext pluginContext) - { - if (pluginContext == null || !Dictionary.ContainsKey(pluginContext)) - { - return Enumerable.Empty(); - } - - return Dictionary[pluginContext].Values; - } - - /// - /// Boots the modules of a plugin. - /// - /// The context of the plugin containing the modules. - public void Boot(IPluginContext pluginContext) - { - foreach (var moduleItem in GetModuleItems(pluginContext)) - { - // initialize module - moduleItem.Boot(); - } - } - - /// - /// Terminate modules of a plugin. - /// - /// The context of the plugin containing the modules. - public void ShutDown(IPluginContext pluginContext) - { - foreach (var moduleItem in GetModuleItems(pluginContext)) - { - // terminate module - moduleItem.ShutDown(); - } - } - - /// - /// Terminate modules of a application. - /// - /// The context of the application containing the modules. - public void ShutDown(IApplicationContext applicationContext) - { - foreach (var moduleItem in Dictionary.Values.SelectMany(x => x.Values)) - { - // terminate module - moduleItem.ShutDown(applicationContext); - } - } - - /// - /// Removes all modules associated with the specified plugin context. - /// - /// The context of the plugin that contains the modules to remove. - public void Remove(IPluginContext pluginContext) - { - if (!Dictionary.ContainsKey(pluginContext)) - { - return; - } - - ShutDown(pluginContext); - - foreach (var moduleItem in Dictionary[pluginContext].Values) - { - moduleItem.Dispose(); - } - - Dictionary.Remove(pluginContext); - } - - /// - /// Raises the AddModule event. - /// - /// The module context. - private void OnAddModule(IModuleContext moduleContext) - { - AddModule?.Invoke(this, moduleContext); - } - - /// - /// Raises the RemoveModule event. - /// - /// The module context. - private void OnRemoveModule(IModuleContext moduleContext) - { - RemoveModule?.Invoke(this, moduleContext); - } - - /// - /// Information about the component is collected and prepared for output in the log. - /// - /// The context of the plugin. - /// A list of log entries. - /// The shaft deep. - public void PrepareForLog(IPluginContext pluginContext, IList output, int deep) - { - foreach (var moduleContext in GetModuleItems(pluginContext)) - { - output.Add - ( - string.Empty.PadRight(deep) + - InternationalizationManager.I18N - ( - "webexpress:modulemanager.module", - moduleContext.ModuleId, - string.Join(",", moduleContext.Applications) - ) - ); - } - } - } -} diff --git a/src/WebExpress.WebCore/WebPackage/IPackageManager.cs b/src/WebExpress.WebCore/WebPackage/IPackageManager.cs new file mode 100644 index 0000000..315e6d6 --- /dev/null +++ b/src/WebExpress.WebCore/WebPackage/IPackageManager.cs @@ -0,0 +1,27 @@ +using System; +using WebExpress.WebCore.WebComponent; +using WebExpress.WebCore.WebPackage.Model; + +namespace WebExpress.WebCore.WebPackage +{ + /// + /// The package manager manages packages with WebExpress extensions. The packages must be in WebExpressPackage format (*.wxp). + /// + public interface IPackageManager : IComponentManager + { + /// + /// An event that fires when an package is added. + /// + event EventHandler AddPackage; + + /// + /// An event that fires when an package is removed. + /// + event EventHandler RemovePackage; + + /// + /// Returns the catalog of installed packages. + /// + PackageCatalog Catalog { get; } + } +} diff --git a/src/WebExpress.WebCore/WebPackage/PackageCatalog.cs b/src/WebExpress.WebCore/WebPackage/Model/PackageCatalog.cs similarity index 77% rename from src/WebExpress.WebCore/WebPackage/PackageCatalog.cs rename to src/WebExpress.WebCore/WebPackage/Model/PackageCatalog.cs index dded64f..05bab46 100644 --- a/src/WebExpress.WebCore/WebPackage/PackageCatalog.cs +++ b/src/WebExpress.WebCore/WebPackage/Model/PackageCatalog.cs @@ -3,8 +3,11 @@ using System.Linq; using System.Xml.Serialization; -namespace WebExpress.WebCore.WebPackage +namespace WebExpress.WebCore.WebPackage.Model { + /// + /// Represents a catalog of packages. + /// [XmlRoot("catalog")] public class PackageCatalog { @@ -12,20 +15,20 @@ public class PackageCatalog /// Returns the package entries in the catalog. /// [XmlElement("package")] - public List Packages { get; } = new List(); + public List Packages { get; } = []; /// /// Returns the system package entries in the catalog. /// [XmlIgnore] - public List SystemPackages { get; } = new List(); + public List SystemPackages { get; } = []; /// /// Locates a specific catalog item. /// /// The package id. /// The catalog item or null. - public PackageCatalogItem Find(string id) + public PackageCatalogItem Find(string id) { return Packages .Where(x => x.Id.Equals(id, StringComparison.OrdinalIgnoreCase)) diff --git a/src/WebExpress.WebCore/WebPackage/PackageCatalogItem.cs b/src/WebExpress.WebCore/WebPackage/Model/PackageCatalogItem.cs similarity index 90% rename from src/WebExpress.WebCore/WebPackage/PackageCatalogItem.cs rename to src/WebExpress.WebCore/WebPackage/Model/PackageCatalogItem.cs index c47daf0..c4d3b54 100644 --- a/src/WebExpress.WebCore/WebPackage/PackageCatalogItem.cs +++ b/src/WebExpress.WebCore/WebPackage/Model/PackageCatalogItem.cs @@ -2,8 +2,11 @@ using System.Xml.Serialization; using WebExpress.WebCore.WebPlugin; -namespace WebExpress.WebCore.WebPackage +namespace WebExpress.WebCore.WebPackage.Model { + /// + /// Represents an item in the package catalog. + /// [XmlRoot("package")] public class PackageCatalogItem { @@ -29,7 +32,7 @@ public class PackageCatalogItem /// Returns the plugins belonging to the package. /// [XmlIgnore] - public List Plugins { get; internal set; } = new List(); + public List Plugins { get; internal set; } = []; /// /// Returns the meta information about the package. diff --git a/src/WebExpress.WebCore/WebPackage/Model/PackageCatalogeItemState.cs b/src/WebExpress.WebCore/WebPackage/Model/PackageCatalogeItemState.cs new file mode 100644 index 0000000..0caf616 --- /dev/null +++ b/src/WebExpress.WebCore/WebPackage/Model/PackageCatalogeItemState.cs @@ -0,0 +1,23 @@ +namespace WebExpress.WebCore.WebPackage.Model +{ + /// + /// Represents the state of a package in the catalog. + /// + public enum PackageCatalogeItemState + { + /// + /// The package is available but has not yet been loaded by WebExpress. + /// + Available, + + /// + /// The package has been loaded and is ready for use. + /// + Active, + + /// + /// The package has been disabled. The use of the package is not possible. + /// + Disable + } +} diff --git a/src/WebExpress.WebCore/WebPackage/PackageItem.cs b/src/WebExpress.WebCore/WebPackage/Model/PackageItem.cs similarity index 78% rename from src/WebExpress.WebCore/WebPackage/PackageItem.cs rename to src/WebExpress.WebCore/WebPackage/Model/PackageItem.cs index 33ac150..5df0709 100644 --- a/src/WebExpress.WebCore/WebPackage/PackageItem.cs +++ b/src/WebExpress.WebCore/WebPackage/Model/PackageItem.cs @@ -1,7 +1,10 @@ using System.Collections.Generic; -namespace WebExpress.WebCore.WebPackage +namespace WebExpress.WebCore.WebPackage.Model { + /// + /// Represents an item in a web package. + /// public class PackageItem { /// @@ -60,10 +63,19 @@ public class PackageItem public IEnumerable PluginSources { get; set; } /// - /// Constructor + /// Initializes a new instance of the class. /// internal PackageItem() { } + + /// + /// Convert the package element to a string. + /// + /// The package element in its string representation. + public override string ToString() + { + return $"Package '{Id}'"; + } } } diff --git a/src/WebExpress.WebCore/WebPackage/PackageItemSpec.cs b/src/WebExpress.WebCore/WebPackage/Model/PackageItemSpec.cs similarity index 98% rename from src/WebExpress.WebCore/WebPackage/PackageItemSpec.cs rename to src/WebExpress.WebCore/WebPackage/Model/PackageItemSpec.cs index c2762df..aad4181 100644 --- a/src/WebExpress.WebCore/WebPackage/PackageItemSpec.cs +++ b/src/WebExpress.WebCore/WebPackage/Model/PackageItemSpec.cs @@ -1,6 +1,6 @@ using System.Xml.Serialization; -namespace WebExpress.WebCore.WebPackage +namespace WebExpress.WebCore.WebPackage.Model { /// /// The package specification is an XML file containing the specification of the package. diff --git a/src/WebExpress.WebCore/WebPackage/PackageBuilder.cs b/src/WebExpress.WebCore/WebPackage/PackageBuilder.cs index e77b905..e601d24 100644 --- a/src/WebExpress.WebCore/WebPackage/PackageBuilder.cs +++ b/src/WebExpress.WebCore/WebPackage/PackageBuilder.cs @@ -3,6 +3,7 @@ using System.IO.Compression; using System.Linq; using System.Xml.Serialization; +using WebExpress.WebCore.WebPackage.Model; namespace WebExpress.WebCore.WebPackage { diff --git a/src/WebExpress.WebCore/WebPackage/PackageCatalogeItemState.cs b/src/WebExpress.WebCore/WebPackage/PackageCatalogeItemState.cs deleted file mode 100644 index 34ce0b7..0000000 --- a/src/WebExpress.WebCore/WebPackage/PackageCatalogeItemState.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace WebExpress.WebCore.WebPackage -{ - public enum PackageCatalogeItemState - { - /// - /// Das Paket ist verfügbar, jedoch noch nicht vom WebExpress geladen. - /// - Available, - - /// - /// Das Paket wurde geladen und steht zur Nutzung bereit. - /// - Active, - - /// - /// Das Paket wurde deaktiviert. Die Nutzung des Paketes ist nicht möglich. - /// - Disable - } -} diff --git a/src/WebExpress.WebCore/WebPackage/PackageManager.cs b/src/WebExpress.WebCore/WebPackage/PackageManager.cs index f56b876..8571f43 100644 --- a/src/WebExpress.WebCore/WebPackage/PackageManager.cs +++ b/src/WebExpress.WebCore/WebPackage/PackageManager.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.IO.Compression; using System.Linq; @@ -12,6 +13,8 @@ using System.Xml.Serialization; using WebExpress.WebCore.Internationalization; using WebExpress.WebCore.WebComponent; +using WebExpress.WebCore.WebLog; +using WebExpress.WebCore.WebPackage.Model; using WebExpress.WebCore.WebPlugin; namespace WebExpress.WebCore.WebPackage @@ -19,22 +22,21 @@ namespace WebExpress.WebCore.WebPackage /// /// The package manager manages packages with WebExpress extensions. The packages must be in WebExpressPackage format (*.wxp). /// - public sealed class PackageManager : IComponent, ISystemComponent + public sealed class PackageManager : IPackageManager, ISystemComponent { + private readonly ComponentHub _componentHub; + private readonly IHttpServerContext _httpServerContext; + private readonly PluginManager _pluginManager; + /// /// An event that fires when an package is added. /// - public static event EventHandler AddPackage; + public event EventHandler AddPackage; /// /// An event that fires when an package is removed. /// - public static event EventHandler RemovePackage; - - /// - /// Returns or sets the reference to the context of the host. - /// - public IHttpServerContext HttpServerContext { get; private set; } + public event EventHandler RemovePackage; /// /// Thread Termination. @@ -44,27 +46,25 @@ public sealed class PackageManager : IComponent, ISystemComponent /// /// Returns the catalog of installed packages. /// - private PackageCatalog Catalog { get; } = new PackageCatalog(); + public PackageCatalog Catalog { get; } = new PackageCatalog(); /// - /// Constructor + /// Initializes a new instance of the class. /// - internal PackageManager() + /// The component hub. + /// The plugin manager. + /// The reference to the context of the host. + [SuppressMessage("CodeQuality", "IDE0051:Remove unused private members", Justification = "Used via Reflection.")] + private PackageManager(IComponentHub componentHub, IPluginManager pluginManager, IHttpServerContext httpServerContext) { + _componentHub = componentHub as ComponentHub; + _pluginManager = pluginManager as PluginManager; - } - - /// - /// Initialization - /// - /// The reference to the context of the host. - public void Initialization(IHttpServerContext context) - { - HttpServerContext = context; + _httpServerContext = httpServerContext; - HttpServerContext.Log.Debug + _httpServerContext.Log.Debug ( - InternationalizationManager.I18N("webexpress:packagemanager.initialization") + I18N.Translate("webexpress.webcore:packagemanager.initialization") ); } @@ -74,22 +74,22 @@ public void Initialization(IHttpServerContext context) internal void Execute() { // load the default plugins - ComponentManager.PluginManager.Register(); + _pluginManager.Register(); // boot default elements - ComponentManager.BootComponent(ComponentManager.PluginManager.Plugins); + _componentHub.BootComponent(_pluginManager.Plugins); LoadCatalog(); foreach (var package in Catalog.Packages) { - var packagesFromFile = LoadPackage(Path.Combine(HttpServerContext.PackagePath, package.File)); + var packagesFromFile = LoadPackage(Path.Combine(_httpServerContext.PackagePath, package.File)); package.Metadata = packagesFromFile?.Metadata; - HttpServerContext.Log.Debug + _httpServerContext.Log.Debug ( - InternationalizationManager.I18N("webexpress:packagemanager.existing", package.File) + I18N.Translate("webexpress.webcore:packagemanager.existing", package.File) ); if (package.State != PackageCatalogeItemState.Disable) @@ -104,7 +104,7 @@ internal void Execute() SaveCatalog(); // build sitemap - ComponentManager.SitemapManager.Refresh(); + _componentHub.SitemapManager.Refresh(); Task.Factory.StartNew(() => { @@ -132,17 +132,17 @@ public void ShutDown() /// public void Scan() { - HttpServerContext.Log.Debug + _httpServerContext.Log.Debug ( - InternationalizationManager.I18N + I18N.Translate ( - "webexpress:packagemanager.scan", - HttpServerContext.PackagePath + "webexpress.webcore:packagemanager.scan", + _httpServerContext.PackagePath ) ); // determine all WebExpress packages from the file system - var packageFiles = Directory.GetFiles(HttpServerContext.PackagePath, "*.wxp").Select(x => Path.GetFileName(x)).ToList(); + var packageFiles = Directory.GetFiles(_httpServerContext.PackagePath, "*.wxp").Select(x => Path.GetFileName(x)).ToList(); // all packages that are not yet installed var newPackages = packageFiles.Except(Catalog.Packages.Where(x => x != null).Select(x => x.File)).ToList(); @@ -155,7 +155,7 @@ public void Scan() foreach (var package in newPackages) { - var packagesFromFile = LoadPackage(Path.Combine(HttpServerContext.PackagePath, package)); + var packagesFromFile = LoadPackage(Path.Combine(_httpServerContext.PackagePath, package)); ExtractPackage(packagesFromFile); RegisterPackage(packagesFromFile); @@ -163,11 +163,11 @@ public void Scan() Catalog.Packages.Add(packagesFromFile); - HttpServerContext.Log.Debug + _httpServerContext.Log.Debug ( - InternationalizationManager.I18N + I18N.Translate ( - "webexpress:packagemanager.add", + "webexpress.webcore:packagemanager.add", package ) ); @@ -175,15 +175,15 @@ public void Scan() foreach (var package in removePackages) { - var packagesFromFile = LoadPackage(Path.Combine(HttpServerContext.PackagePath, package)); + var packagesFromFile = LoadPackage(Path.Combine(_httpServerContext.PackagePath, package)); Catalog.Packages.Add(packagesFromFile); - HttpServerContext.Log.Debug + _httpServerContext.Log.Debug ( - InternationalizationManager.I18N + I18N.Translate ( - "webexpress:packagemanager.remove", + "webexpress.webcore:packagemanager.remove", package ) ); @@ -220,10 +220,10 @@ public void Scan() // } //} - if (newPackages.Any() || removePackages.Any()) + if (newPackages.Count != 0 || removePackages.Count != 0) { // build sitemap - ComponentManager.SitemapManager.Refresh(); + _componentHub.SitemapManager.Refresh(); // save the catalog SaveCatalog(); @@ -292,14 +292,14 @@ private PackageCatalogItem LoadPackage(string file) } catch (Exception ex) { - HttpServerContext.Log.Exception(ex); + _httpServerContext.Log.Exception(ex); } - HttpServerContext.Log.Debug + _httpServerContext.Log.Debug ( - InternationalizationManager.I18N + I18N.Translate ( - "webexpress:packagemanager.packagenotfound", + "webexpress.webcore:packagemanager.packagenotfound", file ) ); @@ -312,10 +312,16 @@ private PackageCatalogItem LoadPackage(string file) /// private void LoadCatalog() { - var catalogeFile = Path.Combine(HttpServerContext.PackagePath, "catalog.xml"); + var catalogeFile = Path.Combine(_httpServerContext.PackagePath, "catalog.xml"); if (File.Exists(catalogeFile)) { using var catalog = new StreamReader(catalogeFile); + + if (catalog.BaseStream.Length == 0) + { + return; + } + var serializer = new XmlSerializer(typeof(PackageCatalog)); var items = (PackageCatalog)serializer.Deserialize(catalog); @@ -323,6 +329,8 @@ private void LoadCatalog() //Catalog.Packages.RemoveAll(x => !x.System); Catalog.Packages.AddRange(items.Packages); } + + Log(); } /// @@ -330,18 +338,18 @@ private void LoadCatalog() /// private void SaveCatalog() { - var catalogeFile = Path.Combine(HttpServerContext.PackagePath, "catalog.xml"); + var catalogeFile = Path.Combine(_httpServerContext.PackagePath, "catalog.xml"); using var fs = new FileStream(catalogeFile, FileMode.Create); using var writer = new XmlTextWriter(fs, Encoding.Unicode); var serializer = new XmlSerializer(typeof(PackageCatalog)); writer.Formatting = Formatting.Indented; - serializer.Serialize(writer, Catalog, new XmlSerializerNamespaces(new[] { new XmlQualifiedName("", "") })); + serializer.Serialize(writer, Catalog, new XmlSerializerNamespaces([new XmlQualifiedName("", "")])); - HttpServerContext.Log.Debug + _httpServerContext.Log.Debug ( - InternationalizationManager.I18N("webexpress:packagemanager.save") + I18N.Translate("webexpress.webcore:packagemanager.save") ); } @@ -351,14 +359,14 @@ private void SaveCatalog() /// The package. private void ExtractPackage(PackageCatalogItem package) { - var packageFile = Path.Combine(HttpServerContext.PackagePath, package?.File); + var packageFile = Path.Combine(_httpServerContext.PackagePath, package?.File); if (File.Exists(packageFile)) { using var zip = ZipFile.Open(packageFile, ZipArchiveMode.Read); var specEntry = zip.Entries.Where(x => Path.GetExtension(x.FullName) == ".spec").FirstOrDefault(); - var extractedPath = Path.Combine(HttpServerContext.PackagePath, Path.GetFileNameWithoutExtension(package?.File)); + var extractedPath = Path.Combine(_httpServerContext.PackagePath, Path.GetFileNameWithoutExtension(package?.File)); if (!Directory.Exists(extractedPath)) { @@ -371,7 +379,7 @@ private void ExtractPackage(PackageCatalogItem package) { var entryFileName = Path.Combine(extractedPath, entry?.FullName); - if (entryFileName.EndsWith("/")) + if (entryFileName.EndsWith('/')) { if (!Directory.Exists(entryFileName)) { @@ -401,14 +409,12 @@ private void ExtractPackage(PackageCatalogItem package) private void RegisterPackage(PackageCatalogItem package) { // load plugins - foreach (var plugin in package?.Metadata.PluginSources ?? Enumerable.Empty()) + foreach (var plugin in package?.Metadata.PluginSources ?? []) { - var pluginContexts = ComponentManager.PluginManager.Register(GetTargetPath(package, plugin)); + var pluginContexts = _pluginManager.Register(GetTargetPath(package, plugin)); package.Plugins.AddRange(pluginContexts); } - - ComponentManager.LogStatus(); } /// @@ -417,7 +423,7 @@ private void RegisterPackage(PackageCatalogItem package) /// The package. private void BootPackage(PackageCatalogItem package) { - ComponentManager.BootComponent(package.Plugins); + _componentHub.BootComponent(package.Plugins); } /// @@ -430,7 +436,7 @@ private string GetTargetPath(PackageCatalogItem package, string plugin) { return Path.GetFullPath(Path.Combine ( - HttpServerContext.PackagePath, + _httpServerContext.PackagePath, Path.GetFileNameWithoutExtension(package?.File), plugin, GetTFM(), $"{Path.GetFileName(plugin)}.dll" )); } @@ -439,7 +445,7 @@ private string GetTargetPath(PackageCatalogItem package, string plugin) /// Determines the target framework. /// /// The TFM - private string GetTFM() + private static string GetTFM() { var targetFrameworkAttribute = Assembly.GetExecutingAssembly() .GetCustomAttributes(typeof(TargetFrameworkAttribute), false) @@ -470,10 +476,34 @@ private void OnRemovePackage(PackageCatalogItem item) /// /// Information about the component is collected and prepared for output in the log. /// - /// The context of the plugin. - /// A list of log entries. - /// The shaft deep. - public void PrepareForLog(IPluginContext pluginContext, IList output, int deep) + private void Log() + { + if (Catalog.Packages.Count == 0) + { + return; + } + + using var frame = new LogFrameSimple(_httpServerContext.Log); + var list = new List + { + I18N.Translate("webexpress.webcore:packagemanager.titel") + }; + + foreach (var package in Catalog.Packages) + { + list.Add + ( + I18N.Translate("webexpress.webcore:packagemanager.package", package.Id) + ); + } + + _httpServerContext.Log.Info(string.Join(Environment.NewLine, list)); + } + + /// + /// Release of unmanaged resources reserved during use. + /// + public void Dispose() { } } diff --git a/src/WebExpress.WebCore/WebPage/IPage.cs b/src/WebExpress.WebCore/WebPage/IPage.cs index 622738c..f5eb23e 100644 --- a/src/WebExpress.WebCore/WebPage/IPage.cs +++ b/src/WebExpress.WebCore/WebPage/IPage.cs @@ -1,19 +1,25 @@ -using WebExpress.WebCore.WebResource; +using WebExpress.WebCore.WebEndpoint; namespace WebExpress.WebCore.WebPage { - public interface IPage : IResource + /// + /// Defines the contract for a page resource. + /// + public interface IPage : IPage { - /// - /// Returns or sets the page title. - /// - string Title { get; set; } + } + /// + /// Defines the contract for a page resource that can be rendered using a specific context. + /// + /// The type of the visual tree. + public interface IPage : IEndpoint where TVisualTree : IVisualTree + { /// - /// Redirect to another page. - /// The function throws the RedirectException. + /// Processing of the page. /// - /// The uri to redirect to. - void Redirecting(string uri); + /// The context for rendering the page. + /// The visual tree to be rendered. + void Process(IRenderContext renderContext, TVisualTree visualTree); } } diff --git a/src/WebExpress.WebCore/WebPage/IPageContext.cs b/src/WebExpress.WebCore/WebPage/IPageContext.cs new file mode 100644 index 0000000..12e91b0 --- /dev/null +++ b/src/WebExpress.WebCore/WebPage/IPageContext.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using WebExpress.WebCore.WebEndpoint; + +namespace WebExpress.WebCore.WebPage +{ + /// + /// Defines the context for a page, providing access to various related contexts and properties. + /// + public interface IPageContext : IEndpointContext + { + /// + /// Returns the page title. + /// + string PageTitle { get; } + + /// + /// Returns the scope names that provides the page. The scope name + /// is a string with a name (e.g. global, admin), which can be used by elements to + /// determine whether content and how content should be displayed. + /// + IEnumerable Scopes { get; } + } +} diff --git a/src/WebExpress.WebCore/WebPage/IPageManager.cs b/src/WebExpress.WebCore/WebPage/IPageManager.cs new file mode 100644 index 0000000..5ab7e4f --- /dev/null +++ b/src/WebExpress.WebCore/WebPage/IPageManager.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections.Generic; +using WebExpress.WebCore.WebApplication; +using WebExpress.WebCore.WebComponent; +using WebExpress.WebCore.WebPlugin; + +namespace WebExpress.WebCore.WebPage +{ + /// + /// The page manager manages page elements, which can be called with a URI (Uniform Resource Identifier). + /// + public interface IPageManager : IComponentManager + { + /// + /// An event that fires when an page is added. + /// + event EventHandler AddPage; + + /// + /// An event that fires when an page is removed. + /// + event EventHandler RemovePage; + + /// + /// Returns all pages contexts. + /// + IEnumerable Pages { get; } + + /// + /// Returns an enumeration of all containing page contexts of a plugin. + /// + /// A context of a plugin whose pages are to be registered. + /// An enumeration of page contexts. + IEnumerable GetPages(IPluginContext pluginContext); + + /// + /// Returns an enumeration of page contextes. + /// + /// The page type. + /// An enumeration of page contextes. + IEnumerable GetPages() where T : IPage; + + /// + /// Returns an enumeration of page contextes. + /// + /// The page type. + /// An enumeration of page contextes. + IEnumerable GetPages(Type pageType); + + /// + /// Returns an enumeration of page contextes. + /// + /// The page type. + /// The context of the application. + /// An enumeration of page contextes. + IEnumerable GetPages(Type pageType, IApplicationContext applicationContext); + + /// + /// Returns an enumeration of page contextes. + /// + /// The page type. + /// The context of the application. + /// An enumeration of page contextes. + IEnumerable GetPages(IApplicationContext applicationContext) where T : IPage; + + /// + /// Returns the page context. + /// + /// The context of the application. + /// The page id. + /// An page context or null. + IPageContext GetPage(IApplicationContext applicationContext, string pageId); + + /// + /// Returns the page context. + /// + /// The application id. + /// The page id. + /// An page context or null. + IPageContext GetPage(string applicationId, string pageId); + } +} diff --git a/src/WebExpress.WebCore/WebPage/IRenderContext.cs b/src/WebExpress.WebCore/WebPage/IRenderContext.cs new file mode 100644 index 0000000..e55b918 --- /dev/null +++ b/src/WebExpress.WebCore/WebPage/IRenderContext.cs @@ -0,0 +1,26 @@ +using WebExpress.WebCore.WebEndpoint; +using WebExpress.WebCore.WebMessage; + +namespace WebExpress.WebCore.WebPage +{ + /// + /// Represents the interface of the context in which rendering occurs. + /// + public interface IRenderContext + { + /// + /// Returns the endpoint associated with the rendering context. + /// + IEndpoint Endpoint { get; } + + /// + /// Returns the page context. + /// + IPageContext PageContext { get; } + + /// + /// Returns the request. + /// + Request Request { get; } + } +} diff --git a/src/WebExpress.WebCore/WebPage/IVisualTree.cs b/src/WebExpress.WebCore/WebPage/IVisualTree.cs index 6bafcac..72e5d49 100644 --- a/src/WebExpress.WebCore/WebPage/IVisualTree.cs +++ b/src/WebExpress.WebCore/WebPage/IVisualTree.cs @@ -1,74 +1,17 @@ -using System.Collections.Generic; -using WebExpress.WebCore.WebHtml; +using WebExpress.WebCore.WebHtml; namespace WebExpress.WebCore.WebPage { + /// + /// Represents a visual tree for rendering a web page. + /// public interface IVisualTree { /// - /// Returns the favicon. + /// Converts to an HTML representation. /// - List Favicons { get; } - - /// - /// Returns the internal stylesheet. - /// - List Styles { get; } - - /// - /// Returns the links to the java script files to be used, which are inserted in the header. - /// - List HeaderScriptLinks { get; } - - /// - /// Returns the links to the java script files to be used. - /// - List ScriptLinks { get; } - - /// - /// Returns the links to the java script files to be used, which are inserted in the header. - /// - List HeaderScripts { get; } - - /// - /// Returns the links to the java script files to be used. - /// - IDictionary Scripts { get; } - - /// - /// Returns the links to the css files to be used. - /// - List CssLinks { get; } - - /// - /// Returns the meta information. - /// - List> Meta { get; } - - /// - /// Adds a java script. - /// - /// The link of the java script file. - void AddScriptLink(string url); - - /// - /// Adds a java script in the header. - /// - /// The link of the java script file. - void AddHeaderScriptLinks(string url); - - /// - /// Adds or replaces a java script if it exists. - /// - /// The key. - /// The java script code. - void AddScript(string key, string code); - - /// - /// Convert to html. - /// - /// The context for rendering the page. + /// The context for rendering the visual tree. /// The page as html. - IHtmlNode Render(RenderContext context); + IHtmlNode Render(IVisualTreeContext context); } } diff --git a/src/WebExpress.WebCore/WebPage/IVisualTreeContext.cs b/src/WebExpress.WebCore/WebPage/IVisualTreeContext.cs new file mode 100644 index 0000000..87f0abc --- /dev/null +++ b/src/WebExpress.WebCore/WebPage/IVisualTreeContext.cs @@ -0,0 +1,26 @@ +using WebExpress.WebCore.WebMessage; +using WebExpress.WebCore.WebUri; + +namespace WebExpress.WebCore.WebPage +{ + /// + /// Represents the context of a visual tree. + /// + public interface IVisualTreeContext + { + /// + /// Returns the request. + /// + Request Request { get; } + + /// + /// The uri of the request. + /// + UriEndpoint Uri { get; } + + /// + /// Return or sets the render context. + /// + IRenderContext RenderContext { get; } + } +} diff --git a/src/WebExpress.WebCore/WebPage/Model/PageDictionary.cs b/src/WebExpress.WebCore/WebPage/Model/PageDictionary.cs new file mode 100644 index 0000000..2925951 --- /dev/null +++ b/src/WebExpress.WebCore/WebPage/Model/PageDictionary.cs @@ -0,0 +1,289 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using WebExpress.WebCore.WebApplication; +using WebExpress.WebCore.WebPlugin; + +namespace WebExpress.WebCore.WebPage.Model +{ + /// + /// Represents a dictionary that maps plugin contexts to application contexts, page types, and page items. + /// key = plugin context + /// value = application context { key = page type, value = page item } + /// + internal class PageDictionary + { + private readonly Dictionary>> _dict = []; + + /// + /// Returns all page contexts. + /// + public IEnumerable All => _dict.Values + .SelectMany(x => x.Values) + .SelectMany(x => x.Values) + .Select(x => x.PageContext); + + /// + /// Adds a page item to the dictionary. + /// + /// The plugin context. + /// The application context. + /// The page item. + /// True if the page item was added successfully, false if an element with the same status code already exists. + public bool AddPageItem(IPluginContext pluginContext, IApplicationContext applicationContext, PageItem pageItem) + { + var type = pageItem.PageClass; + + if (type.GetInterface(typeof(IPage<>).Name) == null) + { + return false; + } + + if (!_dict.TryGetValue(pluginContext, out Dictionary> appContextDict)) + { + appContextDict = ([]); + _dict[pluginContext] = appContextDict; + } + + if (!appContextDict.TryGetValue(applicationContext, out Dictionary pageDict)) + { + pageDict = ([]); + appContextDict[applicationContext] = pageDict; + } + + if (!pageDict.ContainsKey(type)) + { + pageDict[type] = pageItem; + return true; + } + + return false; + } + + /// + /// Removes all page from the dictionary. + /// + /// The plugin context. + public IEnumerable RemovePage(IPluginContext pluginContext) + { + var removed = GetPageItems(pluginContext); + + _dict.Remove(pluginContext); + + foreach (var item in removed) + { + item.Dispose(); + } + + return removed.Select(x => x.PageContext); + } + + /// + /// Removes all page from the dictionary. + /// + /// The application context. + public IEnumerable RemovePage(IApplicationContext applicationContext) + { + var removed = GetPageItems(applicationContext); + + foreach (var applicationDict in _dict.Values) + { + applicationDict.Remove(applicationContext); + } + + foreach (var item in removed) + { + item.Dispose(); + } + + return removed.Select(x => x.PageContext); + } + + /// + /// Returns the page items associated with the specified plugin context. + /// + /// The plugin context to retrieve page items for. + /// An IEnumerable of associated with the specified application context. + public IEnumerable GetPageItems(IPluginContext pluginContext) + { + return _dict.Where(x => x.Key.Equals(pluginContext)) + .Select(x => x.Value) + .SelectMany(x => x.Values) + .SelectMany(x => x.Values); + } + + /// + /// Returns the page items associated with the specified application context. + /// + /// The application context to retrieve page items for. + /// An IEnumerable of associated with the specified application context. + public IEnumerable GetPageItems(IApplicationContext applicationContext) + { + return _dict.Values + .SelectMany(x => x) + .Where(x => x.Key.Equals(applicationContext)) + .SelectMany(x => x.Value) + .Select(x => x.Value); + } + + /// + /// Returns the page item associated with the specified page context. + /// + /// The context of the page to retrieve. + /// The associated with the specified page context, or null if no such item exists. + public PageItem GetPageItem(IPageContext pageContext) + { + return _dict.Values + .SelectMany(x => x.Values) + .SelectMany(x => x.Values) + .FirstOrDefault(x => x.PageContext.Equals(pageContext)); + } + + /// + /// Returns the page items from the dictionary for a specific application context and page type. + /// + /// The application context. + /// The type of the page. + /// An IEnumerable of page items. + public IEnumerable GetPageItems(IApplicationContext applicationContext, Type pageType) + { + if (!typeof(IPage).IsAssignableFrom(pageType)) + { + return []; + } + + if (_dict.ContainsKey(applicationContext?.PluginContext)) + { + var appContextDict = _dict[applicationContext?.PluginContext]; + + if (appContextDict.TryGetValue(applicationContext, out Dictionary pageDict)) + { + if (pageDict.TryGetValue(pageType, out PageItem value)) + { + return [value]; + } + } + } + + return []; + } + + /// + /// Returns an enumeration of all containing page contexts of a plugin. + /// + /// A context of a plugin whose pages are to be registered. + /// An enumeration of page contexts. + public IEnumerable GetPages(IPluginContext pluginContext) + { + if (_dict.TryGetValue(pluginContext, out var pluginResources)) + { + return pluginResources + .SelectMany(x => x.Value) + .Select(x => x.Value.PageContext); + } + + return []; + } + + /// + /// Returns an enumeration of page contextes. + /// + /// The page type. + /// An enumeration of page contextes. + public IEnumerable GetPages(Type pageType) + { + return _dict.Values + .SelectMany(x => x.Values) + .SelectMany(x => x.Values) + .Where(x => x.PageClass.Equals(pageType)) + .Select(x => x.PageContext); + } + + /// + /// Returns an enumeration of page contextes. + /// + /// The page type. + /// The context of the application. + /// An enumeration of page contextes. + public IEnumerable GetPages(Type pageType, IApplicationContext applicationContext) + { + return _dict.Values + .SelectMany(x => x.Values) + .SelectMany(x => x.Values) + .Where(x => x.PageClass.Equals(pageType)) + .Where(x => x.PageContext.ApplicationContext.Equals(applicationContext)) + .Select(x => x.PageContext); + } + + /// + /// Returns an enumeration of page contextes. + /// + /// The page type. + /// The context of the application. + /// An enumeration of page contextes. + public IEnumerable GetPages(IApplicationContext applicationContext) where TPage : IPage + { + return _dict.Values + .SelectMany(x => x.Values) + .SelectMany(x => x.Values) + .Where(x => x.PageClass.Equals(typeof(TPage))) + .Where(x => x.PageContext.ApplicationContext.Equals(applicationContext)) + .Select(x => x.PageContext); + } + + /// + /// Returns the page context. + /// + /// The context of the application. + /// The page id. + /// An page context or null. + public IPageContext GetPage(IApplicationContext applicationContext, string pageId) + { + return _dict.Values + .SelectMany(x => x.Values) + .SelectMany(x => x.Values) + .Where(x => x.PageContext.ApplicationContext.Equals(applicationContext)) + .Where(x => x.PageContext.EndpointId.Equals(pageId)) + .Select(x => x.PageContext) + .FirstOrDefault(); + } + + /// + /// Returns the page context. + /// + /// The application id. + /// The page id. + /// An page context or null. + public IPageContext GetPage(string applicationId, string pageId) + { + return _dict.Values + .SelectMany(x => x.Values) + .SelectMany(x => x.Values) + .Where(x => x.PageContext.ApplicationContext.ApplicationId.Equals(applicationId)) + .Where(x => x.PageContext.EndpointId.Equals(pageId)) + .Select(x => x.PageContext) + .FirstOrDefault(); + } + + /// + /// Checks if the dictionary contains the specified plugin context. + /// + /// The plugin context to check for. + /// True if the plugin context exists in the dictionary, otherwise false. + public bool Contains(IPluginContext pluginContext) + { + return _dict.ContainsKey(pluginContext); + } + + /// + /// Checks if the dictionary contains the specified plugin context and application context. + /// + /// The plugin context to check for. + /// The application context to check for. + /// True if the plugin context and application context exist in the dictionary, otherwise false. + public bool Contains(IPluginContext pluginContext, IApplicationContext applicationContext) + { + return _dict.TryGetValue(pluginContext, out var appDict) && appDict.ContainsKey(applicationContext); + } + } +} diff --git a/src/WebExpress.WebCore/WebPage/Model/PageItem.cs b/src/WebExpress.WebCore/WebPage/Model/PageItem.cs new file mode 100644 index 0000000..9f27f7e --- /dev/null +++ b/src/WebExpress.WebCore/WebPage/Model/PageItem.cs @@ -0,0 +1,107 @@ +using System; +using System.Collections.Generic; +using WebExpress.WebCore.WebApplication; +using WebExpress.WebCore.WebComponent; +using WebExpress.WebCore.WebCondition; +using WebExpress.WebCore.WebEndpoint; +using WebExpress.WebCore.WebPlugin; + +namespace WebExpress.WebCore.WebPage.Model +{ + /// + /// A page element that contains meta information about a page. + /// + internal class PageItem : IDisposable + { + /// + /// Returns the endpoint id. + /// + public IComponentId EndpointId { get; internal set; } + + /// + /// Returns the associated plugin context. + /// + public IPluginContext PluginContext { get; internal set; } + + /// + /// Returns the corresponding application context. + /// + public IApplicationContext ApplicationContext { get; internal set; } + + /// + /// Returns or sets the resource title. + /// + public string Title { get; set; } + + /// + /// Returns or sets the type of page. + /// + public Type PageClass { get; set; } + + /// + /// Returns or sets the instance of the page, if the page is cached, otherwise null. + /// + public IEndpoint Instance { get; set; } + + /// + /// Returns the scope names that provides the resource. The scope name + /// is a string with a name (e.g. global, admin), which can be used by elements to + /// determine whether content and how content should be displayed. + /// + public IEnumerable Scopes { get; set; } + + /// + /// Returns or sets whether all subpaths should be taken into sitemap. + /// + public bool IncludeSubPaths { get; set; } + + /// + /// Returns the conditions that must be met for the resource to be active. + /// + public IEnumerable Conditions { get; set; } + + /// + /// Returns whether the resource is created once and reused each time it is called. + /// + public bool Cache { get; set; } + + /// + /// Returns whether it is a optional resource. + /// + public bool Optional { get; set; } + + /// + /// Returns the attributes associated with the page. + /// + public IEnumerable Attributes { get; internal set; } + + /// + /// Returns the page context. + /// + public IPageContext PageContext { get; internal set; } + + /// + /// Initializes a new instance of the class. + /// + /// The endpoint manager responsible for managing endpoints. + internal PageItem(IEndpointManager endpointManager) + { + } + + /// + /// Performs application-specific tasks related to sharing, returning, or resetting unmanaged resources. + /// + public void Dispose() + { + } + + /// + /// Convert the resource element to a string. + /// + /// The resource element in its string representation. + public override string ToString() + { + return $"Page: '{PageContext?.EndpointId}'"; + } + } +} diff --git a/src/WebExpress.WebCore/WebPage/Page.cs b/src/WebExpress.WebCore/WebPage/Page.cs index 0bfceae..db4c456 100644 --- a/src/WebExpress.WebCore/WebPage/Page.cs +++ b/src/WebExpress.WebCore/WebPage/Page.cs @@ -1,13 +1,11 @@ -using WebExpress.WebCore.WebMessage; -using WebExpress.WebCore.WebResource; - -namespace WebExpress.WebCore.WebPage +namespace WebExpress.WebCore.WebPage { /// /// The prototype of a website. /// - /// An implementation of the visualization tree. - public abstract class Page : Resource, IPage where T : RenderContext, new() + /// An implementation of the visualization tree. + public abstract class Page : IPage + where TVisualTree : IVisualTree, new() { /// /// Returns or sets the page title. @@ -15,20 +13,15 @@ namespace WebExpress.WebCore.WebPage public string Title { get; set; } /// - /// Constructor + /// Returns the page context. /// - public Page() - { - - } + public IPageContext PageContext { get; private set; } /// - /// Initialization + /// Initializes a new instance of the class. /// - /// The context of the resource. - public override void Initialization(IResourceContext context) + public Page() { - base.Initialization(context); } /// @@ -36,36 +29,21 @@ public override void Initialization(IResourceContext context) /// The function throws the RedirectException. /// /// The uri to redirect to. - public void Redirecting(string uri) + public virtual void Redirecting(string uri) { throw new RedirectException(uri?.ToString()); } /// - /// Processing of the resource. + /// Processing of the page. /// - /// The request. - /// The response. - public override Response Process(Request request) - { - var context = new T() - { - Page = this, - Request = request - }; - - Process(context); - - return new ResponseOK() - { - Content = context.VisualTree.Render(context) - }; - } + /// The context for rendering the page. + /// The visual tree to be rendered. + public abstract void Process(IRenderContext renderContext, TVisualTree visualTree); /// - /// Processing of the resource. + /// Performs application-specific tasks related to sharing, returning, or resetting unmanaged resources. /// - /// The context for rendering the page. - public abstract void Process(T context); + public abstract void Dispose(); } } diff --git a/src/WebExpress.WebCore/WebPage/PageContext.cs b/src/WebExpress.WebCore/WebPage/PageContext.cs new file mode 100644 index 0000000..a350cd3 --- /dev/null +++ b/src/WebExpress.WebCore/WebPage/PageContext.cs @@ -0,0 +1,90 @@ +using System; +using System.Collections.Generic; +using WebExpress.WebCore.WebApplication; +using WebExpress.WebCore.WebComponent; +using WebExpress.WebCore.WebCondition; +using WebExpress.WebCore.WebEndpoint; +using WebExpress.WebCore.WebPlugin; +using WebExpress.WebCore.WebUri; + +namespace WebExpress.WebCore.WebPage +{ + /// + /// Represents the context of a page. + /// + public class PageContext : IPageContext + { + /// + /// Returns the associated plugin context. + /// + public IPluginContext PluginContext { get; internal set; } + + /// + /// Returns the corresponding application context. + /// + public IApplicationContext ApplicationContext { get; internal set; } + + /// + /// Returns the scope names that provides the resource. The scope name + /// is a string with a name (e.g. global, admin), which can be used by elements to + /// determine whether content and how content should be displayed. + /// + public IEnumerable Scopes { get; internal set; } = []; + + /// + /// Returns the conditions that must be met for the resource to be active. + /// + public IEnumerable Conditions { get; internal set; } = []; + + /// + /// Returns the endpoint id. + /// + public IComponentId EndpointId { get; internal set; } + + /// + /// Returns the page title. + /// + public string PageTitle { get; internal set; } + + /// + /// Returns whether the resource is created once and reused each time it is called. + /// + public bool Cache { get; internal set; } + + /// + /// Returns or sets whether all subpaths should be taken into sitemap. + /// + public bool IncludeSubPaths { get; internal set; } + + /// + /// Returns the attributes associated with the page. + /// + public IEnumerable Attributes { get; internal set; } + + /// + /// Returns the context path. + /// + public UriEndpoint ContextPath { get; internal set; } + + /// + /// Returns the internal routing path for the endpoint. + /// + public IRoute Route { get; internal set; } + + /// + /// Initializes a new instance of the class. + /// + public PageContext() + { + } + + /// + /// Returns a string that represents the current object. + /// + /// A string that represents the current object. + public override string ToString() + { + return $"Page: {EndpointId}"; + } + } +} diff --git a/src/WebExpress.WebCore/WebPage/PageManager.cs b/src/WebExpress.WebCore/WebPage/PageManager.cs new file mode 100644 index 0000000..60c7f1e --- /dev/null +++ b/src/WebExpress.WebCore/WebPage/PageManager.cs @@ -0,0 +1,516 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using WebExpress.WebCore.Internationalization; +using WebExpress.WebCore.WebApplication; +using WebExpress.WebCore.WebAttribute; +using WebExpress.WebCore.WebComponent; +using WebExpress.WebCore.WebCondition; +using WebExpress.WebCore.WebEndpoint; +using WebExpress.WebCore.WebMessage; +using WebExpress.WebCore.WebPage.Model; +using WebExpress.WebCore.WebPlugin; +using WebExpress.WebCore.WebScope; + +namespace WebExpress.WebCore.WebPage +{ + /// + /// The page manager manages page elements, which can be called with a URI (Uniform page Identifier). + /// + public class PageManager : IPageManager + { + private readonly IComponentHub _componentHub; + private readonly IHttpServerContext _httpServerContext; + private readonly PageDictionary _dictionary = new(); + private static readonly Dictionary _delegateCache = []; + + /// + /// An event that fires when an page is added. + /// + public event EventHandler AddPage; + + /// + /// An event that fires when an page is removed. + /// + public event EventHandler RemovePage; + + /// + /// Returns all page contexts. + /// + public IEnumerable Pages => _dictionary.All; + + /// + /// Initializes a new instance of the class. + /// + /// The component hub. + /// The reference to the context of the host. + [SuppressMessage("CodeQuality", "IDE0051:Remove unused private members", Justification = "Used via Reflection.")] + private PageManager(IComponentHub componentHub, IHttpServerContext httpServerContext) + { + _componentHub = componentHub; + + _componentHub.PluginManager.AddPlugin += OnAddPlugin; + _componentHub.PluginManager.RemovePlugin += OnRemovePlugin; + _componentHub.ApplicationManager.AddApplication += OnAddApplication; + _componentHub.ApplicationManager.RemoveApplication += OnRemoveApplication; + + var endpointtRegistration = new EndpointRegistration() + { + EndpointResolver = (type, applicationContext) => applicationContext != null ? GetPages(type, applicationContext) : GetPages(type), + EndpointsResolver = () => Pages, + HandleRequest = (request, endpontContext) => + { + var pageInstance = CreatePageInstance(endpontContext as IPageContext); + var pageType = pageInstance.GetType(); + var pageContext = endpontContext as IPageContext; + var renderContext = new RenderContext(pageInstance, pageContext, request); + var visualTreeContext = new VisualTreeContext(renderContext); + + var visualTreeType = pageType.GetInterface(typeof(IPage<>).Name).GetGenericArguments()[0]; + if (!_delegateCache.TryGetValue(pageType, out var del)) + { + // create and compile the expression + var renderContextParam = Expression.Parameter(typeof(IRenderContext), "renderContext"); + var visualTreeParam = Expression.Parameter(visualTreeType, "visualTree"); + var processMethod = pageType.GetMethod("Process", [typeof(IRenderContext), visualTreeType]); + var callProzessMethod = Expression.Call + ( + Expression.Constant(pageInstance), + processMethod, + renderContextParam, + visualTreeParam + ); + var lambda = Expression.Lambda(callProzessMethod, renderContextParam, visualTreeParam) + .Compile(); + + _delegateCache[pageType] = lambda; + del = lambda; + } + + // create visual tree instance + var visualTreeInstance = default(IVisualTree); + var flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; + var constructors = visualTreeType?.GetConstructors(flags); + + if (constructors != null) + { + foreach (var constructor in constructors.OrderByDescending(x => x.GetParameters().Length)) + { + // injection + var parameters = constructor.GetParameters(); + var hubProperties = _componentHub.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance); + var contextIdProperty = pageContext.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance) + .Where(x => x.PropertyType == typeof(IComponentId)) + .FirstOrDefault(); + + var parameterValues = parameters.Select(parameter => + parameter.ParameterType == typeof(IComponentHub) ? componentHub : + parameter.ParameterType == typeof(IHttpServerContext) ? httpServerContext : + parameter.ParameterType == typeof(IPageContext) ? pageContext : + parameter.ParameterType == typeof(IComponentId) ? contextIdProperty?.GetValue(pageContext) : + hubProperties.Where(x => x.PropertyType == parameter.ParameterType) + .FirstOrDefault()? + .GetValue(componentHub) ?? null + ).ToArray(); + + if (constructor.Invoke(parameterValues) is IVisualTree visualTree) + { + visualTreeInstance = visualTree; + } + } + } + else + { + visualTreeInstance = Activator.CreateInstance(); + } + + // execute the cached delegate + del.DynamicInvoke(renderContext, visualTreeInstance); + + return new ResponseOK() + { + Content = visualTreeInstance.Render(visualTreeContext) + }; + } + }; + + AddPage += (sender, e) => endpointtRegistration.AddEndpoint?.Invoke(sender, e); + RemovePage += (sender, e) => endpointtRegistration.RemoveEndpoint?.Invoke(sender, e); + + _componentHub.EndpointManager.Register(endpointtRegistration); + + _httpServerContext = httpServerContext; + + _httpServerContext.Log.Debug + ( + I18N.Translate("webexpress.webcore:pagemanager.initialization") + ); + } + + /// + /// Returns an enumeration of all containing page contexts of a plugin. + /// + /// A context of a plugin whose pages are to be registered. + /// An enumeration of page contexts. + public IEnumerable GetPages(IPluginContext pluginContext) + { + return _dictionary.GetPages(pluginContext); + } + + /// + /// Returns an enumeration of page contextes. + /// + /// The page type. + /// An enumeration of page contextes. + public IEnumerable GetPages() where T : IPage + { + return GetPages(typeof(T)); + } + + /// + /// Returns an enumeration of page contextes. + /// + /// The page type. + /// An enumeration of page contextes. + public IEnumerable GetPages(Type pageType) + { + return _dictionary.GetPages(pageType); + } + + /// + /// Returns an enumeration of page contextes. + /// + /// The page type. + /// The context of the application. + /// An enumeration of page contextes. + public IEnumerable GetPages(Type pageType, IApplicationContext applicationContext) + { + return _dictionary.GetPages(pageType, applicationContext); + } + + /// + /// Returns an enumeration of page contextes. + /// + /// The page type. + /// The context of the application. + /// An enumeration of page contextes. + public IEnumerable GetPages(IApplicationContext applicationContext) where T : IPage + { + return _dictionary.GetPages(applicationContext); + } + + /// + /// Returns the page context. + /// + /// The context of the application. + /// The page id. + /// An page context or null. + public IPageContext GetPage(IApplicationContext applicationContext, string pageId) + { + return _dictionary.GetPage(applicationContext, pageId); + } + + /// + /// Returns the page context. + /// + /// The application id. + /// The page id. + /// An page context or null. + public IPageContext GetPage(string applicationId, string pageId) + { + return _dictionary.GetPage(applicationId, pageId); + } + + /// + /// Creates a new page and returns it. If a page already exists (through caching), the existing instance is returned. + /// + /// The context used for page creation. + /// The created or cached page. + private IEndpoint CreatePageInstance(IPageContext pageContext) + { + var resourceItem = _dictionary.GetPageItem(pageContext); + + if (resourceItem != null && resourceItem.Instance == null) + { + var instance = ComponentActivator.CreateInstance + ( + resourceItem.PageClass, + pageContext, + _httpServerContext, + _componentHub, + pageContext.ApplicationContext + ); + + if (resourceItem.Cache) + { + resourceItem.Instance = instance; + } + + return instance; + } + + return resourceItem?.Instance; + } + + /// + /// Discovers and binds pages to an application. + /// + /// The context of the plugin whose pages are to be associated. + private void Register(IPluginContext pluginContext) + { + if (_dictionary.Contains(pluginContext)) + { + return; + } + + Register(pluginContext, _componentHub.ApplicationManager.GetApplications(pluginContext)); + } + + /// + /// Discovers and binds pages to an application. + /// + /// The context of the application whose pages are to be associated. + private void Register(IApplicationContext applicationContext) + { + foreach (var pluginContext in _componentHub.PluginManager.GetPlugins(applicationContext)) + { + if (_dictionary.Contains(pluginContext, applicationContext)) + { + continue; + } + + Register(pluginContext, [applicationContext]); + } + } + + /// + /// Registers pages for a given plugin and application context. + /// + /// The plugin context. + /// The application context (optional). + private void Register(IPluginContext pluginContext, IEnumerable applicationContexts) + { + var assembly = pluginContext?.Assembly; + + foreach (var pageType in assembly.GetTypes() + .Where(x => x.IsClass == true && x.IsSealed && x.IsPublic) + .Where(x => x.GetInterface(typeof(IPage<>).Name) != null)) + { + var id = pageType.FullName?.ToLower(); + var segment = default(ISegmentAttribute); + var title = pageType.Name; + var includeSubPaths = false; + var scopes = new List(); + var conditions = new List(); + var cache = false; + var attributes = pageType.CustomAttributes + .Where(x => !x.AttributeType.GetInterfaces().Contains(typeof(IEndpointAttribute)) && + !x.AttributeType.GetInterfaces().Contains(typeof(IPageAttribute))); + + foreach (var customAttribute in pageType.CustomAttributes + .Where(x => x.AttributeType.GetInterfaces().Contains(typeof(IEndpointAttribute)))) + { + if (customAttribute.AttributeType.GetInterfaces().Contains(typeof(ISegmentAttribute))) + { + segment = pageType.GetCustomAttributes(customAttribute.AttributeType, false).FirstOrDefault() as ISegmentAttribute; + } + else if (customAttribute.AttributeType == typeof(IncludeSubPathsAttribute)) + { + includeSubPaths = Convert.ToBoolean(customAttribute.ConstructorArguments.FirstOrDefault().Value); + } + else if (customAttribute.AttributeType.Name == typeof(ConditionAttribute<>).Name && customAttribute.AttributeType.Namespace == typeof(ConditionAttribute<>).Namespace) + { + var condition = customAttribute.AttributeType.GenericTypeArguments.FirstOrDefault(); + conditions.Add(Activator.CreateInstance(condition) as ICondition); + } + else if (customAttribute.AttributeType == typeof(CacheAttribute)) + { + cache = true; + } + } + + foreach (var customAttribute in pageType.CustomAttributes + .Where(x => x.AttributeType.GetInterfaces().Contains(typeof(IPageAttribute)))) + { + if (customAttribute.AttributeType == typeof(TitleAttribute)) + { + title = customAttribute.ConstructorArguments.FirstOrDefault().Value?.ToString(); + } + else if (customAttribute.AttributeType.Name == typeof(ScopeAttribute<>).Name && customAttribute.AttributeType.Namespace == typeof(ScopeAttribute<>).Namespace) + { + scopes.Add(customAttribute.AttributeType.GenericTypeArguments.FirstOrDefault()); + } + } + + if (pageType.GetInterfaces().Where(x => x == typeof(IScope)).Any()) + { + scopes.Add(pageType); + } + + // assign the page to existing applications + foreach (var applicationContext in applicationContexts) + { + var prefix = applicationContext.ContextPath.Concat + ( + applicationContext.PluginContext != pluginContext + ? pluginContext.PluginName.ToLower() + : "" + ); + var routePath = EndpointManager.CreateEndpointRoute(pageType, prefix, segment); + var pageContext = new PageContext() + { + EndpointId = new ComponentId(id), + PluginContext = pluginContext, + ApplicationContext = applicationContext, + PageTitle = title, + Route = routePath, + Scopes = scopes, + Cache = cache, + Conditions = conditions, + IncludeSubPaths = includeSubPaths, + Attributes = attributes.Select(x => x.AttributeType) + }; + + var pageItem = new PageItem(_componentHub.EndpointManager) + { + EndpointId = new ComponentId(id), + PluginContext = pluginContext, + ApplicationContext = applicationContext, + PageContext = pageContext, + Title = title, + PageClass = pageType, + Scopes = scopes, + Cache = cache, + Conditions = conditions, + IncludeSubPaths = includeSubPaths, + Attributes = attributes.Select(x => x.AttributeType) + }; + + if (_dictionary.AddPageItem(pluginContext, applicationContext, pageItem)) + { + OnAddPage(pageItem.PageContext); + + _httpServerContext?.Log.Debug + ( + I18N.Translate + ( + "webexpress.webcore:pagemanager.addpage", + id, + applicationContext.ApplicationId + ) + ); + } + } + } + } + + /// + /// Removes all pages associated with the specified plugin context. + /// + /// The context of the plugin that contains the pages to remove. + public void Remove(IPluginContext pluginContext) + { + if (pluginContext == null) + { + return; + } + + // the plugin has not been registered in the manager + foreach (var pageContext in _dictionary.RemovePage(pluginContext)) + { + OnRemovePage(pageContext); + } + } + + /// + /// Removes all pages associated with the specified application context. + /// + /// The context of the application that contains the page to remove. + internal void Remove(IApplicationContext applicationContext) + { + if (applicationContext == null) + { + return; + } + + foreach (var pageContext in _dictionary.RemovePage(applicationContext)) + { + OnRemovePage(pageContext); + } + } + + /// + /// Raises the AddPage event. + /// + /// The page context. + private void OnAddPage(IPageContext resourceContext) + { + AddPage?.Invoke(this, resourceContext); + } + + /// + /// Raises the RemovePage event. + /// + /// The page context. + private void OnRemovePage(IPageContext pageContext) + { + RemovePage?.Invoke(this, pageContext); + } + + /// + /// Raises the event when an plugin is added. + /// + /// The source of the event. + /// The context of the plugin being added. + private void OnAddPlugin(object sender, IPluginContext e) + { + Register(e); + } + + /// + /// Raises the event when a plugin is removed. + /// + /// The source of the event. + /// The context of the plugin being removed. + private void OnRemovePlugin(object sender, IPluginContext e) + { + Remove(e); + } + + /// + /// Raises the event when an application is added. + /// + /// The source of the event. + /// The context of the application being added. + private void OnAddApplication(object sender, IApplicationContext e) + { + Register(e); + } + + /// + /// Raises the event when an application is removed. + /// + /// The source of the event. + /// The context of the application being removed. + private void OnRemoveApplication(object sender, IApplicationContext e) + { + Remove(e); + } + + /// + /// Release of unmanaged resources reserved during use. + /// + public void Dispose() + { + _componentHub.PluginManager.AddPlugin -= OnAddPlugin; + _componentHub.PluginManager.RemovePlugin -= OnRemovePlugin; + _componentHub.ApplicationManager.AddApplication -= OnAddApplication; + _componentHub.ApplicationManager.RemoveApplication -= OnRemoveApplication; + + GC.SuppressFinalize(this); + } + } +} diff --git a/src/WebExpress.WebCore/WebResource/RedirectException.cs b/src/WebExpress.WebCore/WebPage/RedirectException.cs similarity index 51% rename from src/WebExpress.WebCore/WebResource/RedirectException.cs rename to src/WebExpress.WebCore/WebPage/RedirectException.cs index 1ab36ae..5a34808 100644 --- a/src/WebExpress.WebCore/WebResource/RedirectException.cs +++ b/src/WebExpress.WebCore/WebPage/RedirectException.cs @@ -1,24 +1,27 @@ using System; -namespace WebExpress.WebCore.WebResource +namespace WebExpress.WebCore.WebPage { + /// + /// Represents an exception that is thrown to redirect a web page. + /// public class RedirectException : Exception { /// - /// Liefert oder setzt das Weiterleitungsziel + /// Returns or sets the redirection target. /// public string Url { get; set; } /// - /// Bestimmt, ob ein permanete Weiterleitung erfolgen soll + /// Determines whether a permanent redirection should occur. /// public bool Permanet { get; set; } /// - /// Constructor + /// Initializes a new instance of the class. /// - /// Das Weiterleitungsziel - /// true wenn 301 gesendet werden soll, flase für 302 + /// The redirection target. + /// true if 301 should be sent, false for 302. public RedirectException(string url, bool permanent = false) : base("Redirecting to " + url) { diff --git a/src/WebExpress.WebCore/WebPage/RenderContext.cs b/src/WebExpress.WebCore/WebPage/RenderContext.cs index 97b3672..dd11745 100644 --- a/src/WebExpress.WebCore/WebPage/RenderContext.cs +++ b/src/WebExpress.WebCore/WebPage/RenderContext.cs @@ -1,90 +1,66 @@ using System.Globalization; -using WebExpress.WebCore.Internationalization; -using WebExpress.WebCore.WebApplication; +using WebExpress.WebCore.WebEndpoint; using WebExpress.WebCore.WebMessage; -using WebExpress.WebCore.WebResource; using WebExpress.WebCore.WebUri; namespace WebExpress.WebCore.WebPage { - public class RenderContext : II18N + /// + /// Represents the context in which rendering occurs, providing access to the page, request, culture, and visual tree. + /// + public class RenderContext : IRenderContext { /// - /// The page where the control is rendered. + /// Returns the page context. /// - public IPage Page { get; internal set; } + public IPageContext PageContext { get; protected set; } /// /// Returns the request. /// - public Request Request { get; internal set; } - - /// - /// Returns the host context. - /// - public IHttpServerContext Host => Request.ServerContext; + public Request Request { get; protected set; } /// /// The uri of the request. /// - public UriResource Uri => Request.Uri; - - /// - /// Returns the context path. - /// - public UriResource ContextPath => Page?.ResourceContext?.ContextPath; + public UriEndpoint Uri => Request?.Uri; /// /// Returns the culture. /// - public CultureInfo Culture - { - get { return Page?.Culture; } - set { } - } - - /// - /// Provides the context of the associated application. - /// - public IApplicationContext ApplicationContext => Page?.ApplicationContext; - - /// - /// Returns the contents of a page. - /// - public IVisualTree VisualTree { get; protected set; } + public CultureInfo Culture => Request?.Culture; /// - /// Returns the log for writing status messages to the console and to a log file. + /// Returns the endpoint associated with the rendering context. /// - public Log Log { get; private set; } + public IEndpoint Endpoint { get; protected set; } /// - /// Constructor + /// Initializes a new instance of the class. /// public RenderContext() { } /// - /// Constructor + /// Initializes a new instance of the class. /// - /// The page where the control is rendered. - /// The request. - /// The visual tree. - public RenderContext(IPage page, Request request, IVisualTree visualTree) + /// The endpoint associated with the rendering context. + /// The page context. + /// The request associated with the rendering context. + public RenderContext(IEndpoint endpoint, IPageContext pageContext, Request request) { - Page = page; + Endpoint = endpoint; + PageContext = pageContext; Request = request; - VisualTree = visualTree; - Culture = (Page as Resource).Culture; } /// /// Copy-Constructor /// - /// The context to copy./param> + /// The context to copy. public RenderContext(RenderContext context) - : this(context?.Page, context?.Request, context?.VisualTree) + : this(context.Endpoint, context?.PageContext, context?.Request) { } } diff --git a/src/WebExpress.WebCore/WebPage/VisualTree.cs b/src/WebExpress.WebCore/WebPage/VisualTree.cs index 3e8baa0..8daed66 100644 --- a/src/WebExpress.WebCore/WebPage/VisualTree.cs +++ b/src/WebExpress.WebCore/WebPage/VisualTree.cs @@ -2,39 +2,43 @@ using System.Linq; using WebExpress.WebCore.Internationalization; using WebExpress.WebCore.WebHtml; -using WebExpress.WebCore.WebPage; -namespace WebExpress.WebCore.WebResource +namespace WebExpress.WebCore.WebPage { /// /// The content of a page is determined by the visual tree. /// - public abstract class VisualTree : IVisualTree + public class VisualTree : IVisualTree { + /// + /// Returns the title of the html document. + /// + public string Title { get; set; } + /// /// Returns the favicons. /// - public List Favicons { get; } = new List(); + public List Favicons { get; } = []; /// /// Returns the internal stylesheet. /// - public List Styles { get; } = new List(); + public List Styles { get; } = []; /// /// Returns the links to the java script files to be used, which are inserted in the header. /// - public List HeaderScriptLinks { get; } = new List(); + public List HeaderScriptLinks { get; } = []; /// /// Returns the links to the java script files to be used. /// - public List ScriptLinks { get; } = new List(); + public List ScriptLinks { get; } = []; /// /// Returns the links to the java script files to be used, which are inserted in the header. /// - public List HeaderScripts { get; } = new List(); + public List HeaderScripts { get; } = []; /// /// Returns the links to the java script files to be used. @@ -44,20 +48,20 @@ public abstract class VisualTree : IVisualTree /// /// Returns the links to the css files to be used. /// - public List CssLinks { get; } = new List(); + public List CssLinks { get; } = []; /// /// Returns the meta information. /// - public List> Meta { get; } = new List>(); + public List> Meta { get; } = []; /// - /// Returns the content. + /// Returns or sets the content. /// - public IHtmlNode Content { get; } + public IHtmlNode Content { get; set; } /// - /// Constructor + /// Initializes a new instance of the class. /// public VisualTree() { @@ -104,21 +108,20 @@ public virtual void AddHeaderScriptLinks(string url) } /// - /// Convert to html. + /// Converts to an HTML representation. /// - /// The context for rendering the page. + /// The context for rendering the visual tree. /// The page as an html tree. - public virtual IHtmlNode Render(RenderContext context) + public virtual IHtmlNode Render(IVisualTreeContext context) { var html = new HtmlElementRootHtml(); - html.Head.Title = InternationalizationManager.I18N(context.Request, context.Page?.Title); + html.Head.Title = I18N.Translate(context.Request, Title); html.Head.Favicons = Favicons?.Select(x => new Favicon(x.Url, x.Mediatype)); - //html.Head.Base = Context.ContextPath.ToString(); html.Head.Styles = Styles; html.Head.Meta = Meta; html.Head.Scripts = HeaderScripts; - html.Body.Elements.Add(Content); - html.Body.Scripts = Scripts.Values.ToList(); + html.Body.Add(Content); + html.Body.Scripts = [.. Scripts.Values]; html.Head.CssLinks = CssLinks.Where(x => x != null).Select(x => x.ToString()); html.Head.ScriptLinks = HeaderScriptLinks?.Where(x => x != null).Select(x => x.ToString()); diff --git a/src/WebExpress.WebCore/WebPage/VisualTreeContext.cs b/src/WebExpress.WebCore/WebPage/VisualTreeContext.cs new file mode 100644 index 0000000..59beca1 --- /dev/null +++ b/src/WebExpress.WebCore/WebPage/VisualTreeContext.cs @@ -0,0 +1,35 @@ +using WebExpress.WebCore.WebMessage; +using WebExpress.WebCore.WebUri; + +namespace WebExpress.WebCore.WebPage +{ + /// + /// Represents the context of a visual tree. + /// + public class VisualTreeContext : IVisualTreeContext + { + /// + /// Returns the request. + /// + public Request Request => RenderContext?.Request; + + /// + /// The uri of the request. + /// + public UriEndpoint Uri => RenderContext?.Request?.Uri; + + /// + /// Return or sets the render context. + /// + public IRenderContext RenderContext { get; protected set; } + + /// + /// Initializes a new instance of the class. + /// + /// The context to copy. + public VisualTreeContext(IRenderContext context) + { + RenderContext = context; + } + } +} diff --git a/src/WebExpress.WebCore/WebPlugin/IPlugin.cs b/src/WebExpress.WebCore/WebPlugin/IPlugin.cs index f1795c6..bcc96d2 100644 --- a/src/WebExpress.WebCore/WebPlugin/IPlugin.cs +++ b/src/WebExpress.WebCore/WebPlugin/IPlugin.cs @@ -1,18 +1,12 @@ -using System; +using WebExpress.WebCore.WebComponent; namespace WebExpress.WebCore.WebPlugin { /// /// This interface represents a plugin. /// - public interface IPlugin : IDisposable + public interface IPlugin : IComponent { - /// - /// Initialization of the plugin. - /// - /// The context. - void Initialization(IPluginContext context); - /// /// Called when the plugin starts working. The call is concurrent. /// diff --git a/src/WebExpress.WebCore/WebPlugin/IPluginContext.cs b/src/WebExpress.WebCore/WebPlugin/IPluginContext.cs index 034df6a..aec3cdd 100644 --- a/src/WebExpress.WebCore/WebPlugin/IPluginContext.cs +++ b/src/WebExpress.WebCore/WebPlugin/IPluginContext.cs @@ -1,12 +1,13 @@ using System.Reflection; -using WebExpress.WebCore.WebUri; +using WebExpress.WebCore.WebComponent; +using WebExpress.WebCore.WebEndpoint; namespace WebExpress.WebCore.WebPlugin { /// /// The context of a plugin. /// - public interface IPluginContext + public interface IPluginContext : IContext { /// /// The assembly that contains the plugin. @@ -16,7 +17,7 @@ public interface IPluginContext /// /// Returns the plugin id. /// - string PluginId { get; } + IComponentId PluginId { get; } /// /// Returns the name of the plugin. @@ -51,11 +52,6 @@ public interface IPluginContext /// /// Returns the icon of the plugin. /// - UriResource Icon { get; } - - /// - /// Returns the host context. - /// - IHttpServerContext Host { get; } + IRoute Icon { get; } } } diff --git a/src/WebExpress.WebCore/WebPlugin/IPluginManager.cs b/src/WebExpress.WebCore/WebPlugin/IPluginManager.cs new file mode 100644 index 0000000..2386890 --- /dev/null +++ b/src/WebExpress.WebCore/WebPlugin/IPluginManager.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using WebExpress.WebCore.WebApplication; +using WebExpress.WebCore.WebComponent; + +namespace WebExpress.WebCore.WebPlugin +{ + /// + /// The plugin manager manages the WebExpress plugins. + /// + public interface IPluginManager : IComponentManager + { + /// + /// An event that fires when an plugin is added. + /// + event EventHandler AddPlugin; + + /// + /// An event that fires when an plugin is removed. + /// + event EventHandler RemovePlugin; + + /// + /// Returns all plugins. + /// + IEnumerable Plugins { get; } + + /// + /// Returns a plugin context based on its id. + /// + /// The id of the plugin. + /// The plugin context. + IPluginContext GetPlugin(string pluginId); + + /// + /// Returns a plugin context based on its id. + /// + /// The type of the plugin. + /// The plugin context. + IPluginContext GetPlugin(Type plugin); + + /// + /// Returns all plugins that have associated applications. + /// + /// The application context to filter plugins. + /// An enumerable collection of plugin contexts with applications. + IEnumerable GetPlugins(IApplicationContext applicationContext); + + /// + /// Returns all ApplicationContext instances associated with a plugin. + /// + /// The context of the plugin. + /// A collection of ApplicationContext instances. + IEnumerable GetAssociatedApplications(IPluginContext pluginContext); + } +} diff --git a/src/WebExpress.WebCore/WebPlugin/PluginDictionary.cs b/src/WebExpress.WebCore/WebPlugin/Model/PluginDictionary.cs similarity index 55% rename from src/WebExpress.WebCore/WebPlugin/PluginDictionary.cs rename to src/WebExpress.WebCore/WebPlugin/Model/PluginDictionary.cs index c794848..fd436af 100644 --- a/src/WebExpress.WebCore/WebPlugin/PluginDictionary.cs +++ b/src/WebExpress.WebCore/WebPlugin/Model/PluginDictionary.cs @@ -1,13 +1,14 @@ using System.Collections.Generic; +using WebExpress.WebCore.WebComponent; -namespace WebExpress.WebCore.WebPlugin +namespace WebExpress.WebCore.WebPlugin.Model { /// /// Verzeichnis über die registrieten Plugins /// Key = PluginId /// Value = Plugin-Metadaten /// - internal class PluginDictionary : Dictionary + internal class PluginDictionary : Dictionary { } } diff --git a/src/WebExpress.WebCore/WebPlugin/PluginItem.cs b/src/WebExpress.WebCore/WebPlugin/Model/PluginItem.cs similarity index 82% rename from src/WebExpress.WebCore/WebPlugin/PluginItem.cs rename to src/WebExpress.WebCore/WebPlugin/Model/PluginItem.cs index e448238..47cd206 100644 --- a/src/WebExpress.WebCore/WebPlugin/PluginItem.cs +++ b/src/WebExpress.WebCore/WebPlugin/Model/PluginItem.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Threading; -namespace WebExpress.WebCore.WebPlugin +namespace WebExpress.WebCore.WebPlugin.Model { /// /// Represents a plugin entry. @@ -32,7 +32,12 @@ internal class PluginItem /// /// The dependencies of the plugin. /// - public IEnumerable Dependencies { get; internal set; } = new List(); + public IEnumerable Dependencies { get; internal set; } = []; + + /// + /// The types of applications that the plugin supports. + /// + public IEnumerable ApplicationTypes { get; internal set; } = []; /// /// Thread termination token. diff --git a/src/WebExpress.WebCore/WebPlugin/PluginLoadContext.cs b/src/WebExpress.WebCore/WebPlugin/Model/PluginLoadContext.cs similarity index 89% rename from src/WebExpress.WebCore/WebPlugin/PluginLoadContext.cs rename to src/WebExpress.WebCore/WebPlugin/Model/PluginLoadContext.cs index 5cc5865..62499f8 100644 --- a/src/WebExpress.WebCore/WebPlugin/PluginLoadContext.cs +++ b/src/WebExpress.WebCore/WebPlugin/Model/PluginLoadContext.cs @@ -1,8 +1,7 @@ -using System; -using System.Reflection; +using System.Reflection; using System.Runtime.Loader; -namespace WebExpress.WebCore.WebPlugin +namespace WebExpress.WebCore.WebPlugin.Model { /// /// Isolation of plug-in dependencies. @@ -16,7 +15,7 @@ public class PluginLoadContext : AssemblyLoadContext private AssemblyDependencyResolver Resolver { get; set; } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The base path of the plugin. public PluginLoadContext(string pluginPath) @@ -46,7 +45,7 @@ protected override Assembly Load(AssemblyName assemblyName) /// /// Name of the unmanaged library. Typically this is the filename without its path or extensions. /// A handle to the loaded library, or Zero. - protected override IntPtr LoadUnmanagedDll(string unmanagedDllName) + protected override nint LoadUnmanagedDll(string unmanagedDllName) { string libraryPath = Resolver.ResolveUnmanagedDllToPath(unmanagedDllName); @@ -55,7 +54,7 @@ protected override IntPtr LoadUnmanagedDll(string unmanagedDllName) return LoadUnmanagedDllFromPath(libraryPath); } - return IntPtr.Zero; + return nint.Zero; } } } diff --git a/src/WebExpress.WebCore/WebPlugin/Plugin.cs b/src/WebExpress.WebCore/WebPlugin/Plugin.cs new file mode 100644 index 0000000..4e7406d --- /dev/null +++ b/src/WebExpress.WebCore/WebPlugin/Plugin.cs @@ -0,0 +1,43 @@ +namespace WebExpress.WebCore.WebPlugin +{ + /// + /// This represents an plugin. + /// + public abstract class Plugin : IPlugin + { + /// + /// Returns the context of the plugin. + /// + public IPluginContext PluginContext { get; private set; } + + /// + /// Initializes a new instance of the class. + /// + public Plugin() + { + } + + /// + /// Initialization of the application. Here, for example, managed resources can be loaded. + /// + /// The context that applies to the execution of the application + public virtual void Initialization(IPluginContext pluginContext) + { + PluginContext = pluginContext; + } + + /// + /// Called when the application starts working. The call is concurrent. + /// + public virtual void Run() + { + } + + /// + /// Release unmanaged resources that have been reserved during use. + /// + public virtual void Dispose() + { + } + } +} diff --git a/src/WebExpress.WebCore/WebPlugin/PluginContext.cs b/src/WebExpress.WebCore/WebPlugin/PluginContext.cs index cb37966..039769b 100644 --- a/src/WebExpress.WebCore/WebPlugin/PluginContext.cs +++ b/src/WebExpress.WebCore/WebPlugin/PluginContext.cs @@ -1,8 +1,12 @@ using System.Reflection; -using WebExpress.WebCore.WebUri; +using WebExpress.WebCore.WebComponent; +using WebExpress.WebCore.WebEndpoint; namespace WebExpress.WebCore.WebPlugin { + /// + /// Represents the context of a plugin, providing access to its metadata and host context. + /// public class PluginContext : IPluginContext { /// @@ -13,7 +17,7 @@ public class PluginContext : IPluginContext /// /// Returns the plugin id. /// - public string PluginId { get; internal set; } + public IComponentId PluginId { get; internal set; } /// /// Returns the name of the plugin. @@ -48,15 +52,10 @@ public class PluginContext : IPluginContext /// /// Returns the icon of the plugin. /// - public UriResource Icon { get; internal set; } + public IRoute Icon { get; internal set; } /// - /// Returns the host context. - /// - public IHttpServerContext Host { get; internal set; } - - /// - /// Constructor + /// Initializes a new instance of the class. /// public PluginContext() { @@ -68,7 +67,7 @@ public PluginContext() /// The string that uniquely represents the plugin. public override string ToString() { - return PluginId; + return $"Plugin: {PluginId}"; } } } diff --git a/src/WebExpress.WebCore/WebPlugin/PluginManager.cs b/src/WebExpress.WebCore/WebPlugin/PluginManager.cs index a1659ce..e3e7edd 100644 --- a/src/WebExpress.WebCore/WebPlugin/PluginManager.cs +++ b/src/WebExpress.WebCore/WebPlugin/PluginManager.cs @@ -1,21 +1,30 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Reflection; using System.Threading.Tasks; using WebExpress.WebCore.Internationalization; +using WebExpress.WebCore.WebApplication; using WebExpress.WebCore.WebAttribute; using WebExpress.WebCore.WebComponent; -using WebExpress.WebCore.WebUri; +using WebExpress.WebCore.WebEndpoint; +using WebExpress.WebCore.WebLog; +using WebExpress.WebCore.WebPlugin.Model; namespace WebExpress.WebCore.WebPlugin { /// /// The plugin manager manages the WebExpress plugins. /// - public class PluginManager : IComponent, IExecutableElements, ISystemComponent + public sealed class PluginManager : IPluginManager, IExecutableElements, ISystemComponent { + private readonly IComponentHub _componentHub; + private readonly IHttpServerContext _httpServerContext; + private readonly PluginDictionary _dictionary = []; + private readonly PluginDictionary _unfulfilledDependencies = []; + /// /// An event that fires when an plugin is added. /// @@ -26,53 +35,26 @@ public class PluginManager : IComponent, IExecutableElements, ISystemComponent /// public event EventHandler RemovePlugin; - /// - /// Returns or sets the reference to the context of the host. - /// - public IHttpServerContext HttpServerContext { get; private set; } - - /// - /// Returns the directory where the plugins are listed. - /// - private PluginDictionary Dictionary { get; } = new PluginDictionary(); - - /// - /// Plugins that do not meet the dependencies. - /// - private PluginDictionary UnfulfilledDependencies { get; } = new PluginDictionary(); - /// /// Returns all plugins. /// - public ICollection Plugins => Dictionary.Values.Select(x => x.PluginContext).ToList(); + public IEnumerable Plugins => _dictionary.Values.Select(x => x.PluginContext).ToList(); /// - /// Constructor + /// Initializes a new instance of the class. /// - internal PluginManager() + /// The component hub. + /// The reference to the context of the host. + [SuppressMessage("CodeQuality", "IDE0051:Remove unused private members", Justification = "Used via Reflection.")] + private PluginManager(IComponentHub componentHub, IHttpServerContext httpServerContext) { - ComponentManager.AddComponent += (s, e) => - { - //AssignToComponent(e); - }; - - ComponentManager.RemoveComponent += (s, e) => - { - //DetachFromcomponent(e); - }; - } + _componentHub = componentHub; - /// - /// Initialization - /// - /// The reference to the context of the host. - public void Initialization(IHttpServerContext context) - { - HttpServerContext = context; + _httpServerContext = httpServerContext; - HttpServerContext.Log.Debug + _httpServerContext.Log.Debug ( - InternationalizationManager.I18N("webexpress:pluginmanager.initialization") + I18N.Translate("webexpress.webcore:pluginmanager.initialization") ); } @@ -94,11 +76,11 @@ internal void Register() if (assembly != null) { assemblies.Add(assembly); - HttpServerContext.Log.Debug + _httpServerContext.Log.Debug ( - InternationalizationManager.I18N + I18N.Translate ( - "webexpress:pluginmanager.load", + "webexpress.webcore:pluginmanager.load", assembly.GetName().Name, assembly.GetName().Version.ToString() ) @@ -112,13 +94,12 @@ internal void Register() } // register plugin - foreach (var assembly in assemblies - .OrderBy(x => x.GetCustomAttribute(typeof(SystemPluginAttribute)) != null ? 0 : 1)) + foreach (var assembly in assemblies.OrderBy(x => x.GetCustomAttribute() != null ? 0 : 1)) { Register(assembly); } - Logging(); + Log(); } /// @@ -145,11 +126,11 @@ internal IEnumerable Register(string pluginFile) if (assembly != null) { assemblies.Add(assembly); - HttpServerContext.Log.Debug + _httpServerContext.Log.Debug ( - InternationalizationManager.I18N + I18N.Translate ( - "webexpress:pluginmanager.load", + "webexpress.webcore:pluginmanager.load", assembly.GetName().Name, assembly.GetName().Version.ToString() ) @@ -165,10 +146,10 @@ internal IEnumerable Register(string pluginFile) foreach (var assembly in assemblies) { var pluginContext = Register(assembly, loadContext); - pluginContexts.Add(pluginContext); + pluginContexts.AddRange(pluginContext); } - Logging(); + Log(); return pluginContexts; } @@ -178,23 +159,69 @@ internal IEnumerable Register(string pluginFile) /// /// The assembly where the plugin is located. /// The plugin load context for isolating and unloading the dependent libraries. - /// A plugin created or null. - private IPluginContext Register(Assembly assembly, PluginLoadContext loadContext = null) + /// A collection of created plugin contexts. + private IEnumerable Register(Assembly assembly, PluginLoadContext loadContext = null) { + var plugins = new List(); + try { + // system plugins without plugin class (e.g. webexpress.webui) + if (assembly.GetCustomAttribute() != null) + { + var id = new ComponentId(assembly.GetName().Name.ToLower()); + var pluginContext = new PluginContext() + { + Assembly = assembly, + PluginId = id, + PluginName = assembly.GetName().Name.ToLower(), + Manufacturer = assembly.GetCustomAttribute()?.Company, + Copyright = assembly.GetCustomAttribute()?.Copyright, + Version = assembly.GetCustomAttribute()?.InformationalVersion + }; + + if (!_dictionary.ContainsKey(id)) + { + _dictionary.Add(id, new PluginItem() + { + PluginLoadContext = loadContext, + PluginClass = assembly.ExportedTypes.FirstOrDefault() ?? typeof(IPlugin), + PluginContext = pluginContext, + Plugin = null, + Dependencies = null, + ApplicationTypes = [typeof(IApplication)] + }); + + _httpServerContext.Log.Debug + ( + I18N.Translate("webexpress.webcore:pluginmanager.created", id) + ); + + OnAddPlugin(pluginContext); + } + else + { + _httpServerContext.Log.Warning + ( + I18N.Translate("webexpress.webcore:pluginmanager.duplicate", id) + ); + } + + plugins.Add(pluginContext); + } + foreach (var type in assembly .GetExportedTypes() .Where(x => x.IsClass && x.IsSealed) - .Where(x => x.GetInterface(typeof(IPlugin).Name) != null) - .Where(x => x.Name.Equals("Plugin"))) + .Where(x => x.GetInterface(typeof(IPlugin).Name) != null)) { - var id = type.Namespace?.ToLower(); + var id = new ComponentId(type.Namespace); var name = type.Assembly.GetCustomAttribute()?.Title; var icon = string.Empty; var description = type.Assembly.GetCustomAttribute()?.Description; var dependencies = new List(); var hasUnfulfilledDependencies = false; + var applicationTypes = new List(); foreach (var customAttribute in type.CustomAttributes .Where(x => x.AttributeType.GetInterfaces().Contains(typeof(IPluginAttribute)))) @@ -215,6 +242,32 @@ private IPluginContext Register(Assembly assembly, PluginLoadContext loadContext { dependencies.Add(customAttribute.ConstructorArguments.FirstOrDefault().Value?.ToString()); } + else if (customAttribute.AttributeType.Name == typeof(ApplicationAttribute<>).Name && customAttribute.AttributeType.Namespace == typeof(ApplicationAttribute<>).Namespace) + { + applicationTypes.Add(customAttribute.AttributeType.GenericTypeArguments.FirstOrDefault()); + } + } + + if (plugins.Count > 0) + { + // to many plugins, only one per assembly + _httpServerContext.Log.Warning + ( + I18N.Translate("webexpress.webcore:pluginmanager.tomany", type.FullName) + ); + + break; + } + + if (applicationTypes.Count == 0) + { + // no application specified + _httpServerContext.Log.Warning + ( + I18N.Translate("webexpress.webcore:pluginmanager.applicationless", id) + ); + + break; } var pluginContext = new PluginContext() @@ -224,40 +277,39 @@ private IPluginContext Register(Assembly assembly, PluginLoadContext loadContext PluginName = name, Manufacturer = type.Assembly.GetCustomAttribute()?.Company, Copyright = type.Assembly.GetCustomAttribute()?.Copyright, - //License = type.Assembly.GetCustomAttribute()?.Copyright, - Icon = UriResource.Combine(HttpServerContext.ContextPath, icon), + Icon = RouteEndpoint.Combine(_httpServerContext?.ContextPath, icon), Description = description, - Version = type.Assembly.GetCustomAttribute()?.InformationalVersion, - Host = HttpServerContext + Version = type.Assembly.GetCustomAttribute()?.InformationalVersion }; - hasUnfulfilledDependencies = HasUnfulfilledDependencies(id, dependencies); + hasUnfulfilledDependencies = HasUnfulfilledDependencies(id, dependencies.Select(x => new ComponentId(x))); if (hasUnfulfilledDependencies) { - UnfulfilledDependencies.Add(id, new PluginItem() + _unfulfilledDependencies.Add(id, new PluginItem() { PluginLoadContext = loadContext, PluginClass = type, PluginContext = pluginContext, - Plugin = Activator.CreateInstance(type) as IPlugin, + Plugin = ComponentActivator.CreateInstance(type, pluginContext, _httpServerContext, _componentHub), Dependencies = dependencies }); } - else if (!Dictionary.ContainsKey(id)) + else if (!_dictionary.ContainsKey(id)) { - Dictionary.Add(id, new PluginItem() + _dictionary.Add(id, new PluginItem() { PluginLoadContext = loadContext, PluginClass = type, PluginContext = pluginContext, - Plugin = Activator.CreateInstance(type) as IPlugin, - Dependencies = dependencies + Plugin = ComponentActivator.CreateInstance(type, pluginContext, _httpServerContext, _componentHub), + Dependencies = dependencies, + ApplicationTypes = applicationTypes }); - HttpServerContext.Log.Debug + _httpServerContext.Log.Debug ( - InternationalizationManager.I18N("webexpress:pluginmanager.created", id) + I18N.Translate("webexpress.webcore:pluginmanager.created", id) ); OnAddPlugin(pluginContext); @@ -266,21 +318,21 @@ private IPluginContext Register(Assembly assembly, PluginLoadContext loadContext } else { - HttpServerContext.Log.Warning + _httpServerContext.Log.Warning ( - InternationalizationManager.I18N("webexpress:pluginmanager.duplicate", id) + I18N.Translate("webexpress.webcore:pluginmanager.duplicate", id) ); } - return pluginContext; + plugins.Add(pluginContext); } } catch (Exception ex) { - HttpServerContext.Log.Exception(ex); + _httpServerContext.Log.Exception(ex); } - return null; + return plugins; } /// @@ -289,10 +341,17 @@ private IPluginContext Register(Assembly assembly, PluginLoadContext loadContext /// The context of the plugin that contains the elemets to remove. public void Remove(IPluginContext pluginContext) { + if (pluginContext == null) + { + return; + } + OnRemovePlugin(pluginContext); var pluginItem = GetPluginItem(pluginContext); pluginItem?.PluginLoadContext?.Unload(); + + _dictionary.Remove(pluginContext.PluginId); } /// @@ -306,27 +365,27 @@ private void CheckUnfulfilledDependencies() { fulfilledDependencies = false; - foreach (var unfulfilledDependencies in UnfulfilledDependencies) + foreach (var unfulfilledDependencies in _unfulfilledDependencies) { var hasUnfulfilledDependencies = HasUnfulfilledDependencies ( unfulfilledDependencies.Key, - unfulfilledDependencies.Value.Dependencies + unfulfilledDependencies.Value.Dependencies.Select(x => new ComponentId(x)) ); if (!hasUnfulfilledDependencies) { fulfilledDependencies = true; - UnfulfilledDependencies.Remove(unfulfilledDependencies.Key); - Dictionary.Add(unfulfilledDependencies.Key, unfulfilledDependencies.Value); + _unfulfilledDependencies.Remove(unfulfilledDependencies.Key); + _dictionary.Add(unfulfilledDependencies.Key, unfulfilledDependencies.Value); OnAddPlugin(unfulfilledDependencies.Value.PluginContext); - HttpServerContext.Log.Debug + _httpServerContext.Log.Debug ( - InternationalizationManager.I18N + I18N.Translate ( - "webexpress:pluginmanager.fulfilleddependencies", + "webexpress.webcore:pluginmanager.fulfilleddependencies", unfulfilledDependencies.Key ) ); @@ -341,21 +400,21 @@ private void CheckUnfulfilledDependencies() /// The id of the plugin. /// The dependencies to check. /// True if dependencies exist, false otherwise - private bool HasUnfulfilledDependencies(string id, IEnumerable dependencies) + private bool HasUnfulfilledDependencies(IComponentId id, IEnumerable dependencies) { var hasUnfulfilledDependencies = false; foreach (var dependency in dependencies - .Where(x => !Dictionary.ContainsKey(x.ToLower()))) + .Where(x => !_dictionary.ContainsKey(x))) { // dependency was not fulfilled hasUnfulfilledDependencies = true; - HttpServerContext.Log.Debug + _httpServerContext.Log.Debug ( - InternationalizationManager.I18N + I18N.Translate ( - "webexpress:pluginmanager.unfulfilleddependencies", + "webexpress.webcore:pluginmanager.unfulfilleddependencies", id, dependency ) @@ -368,20 +427,74 @@ private bool HasUnfulfilledDependencies(string id, IEnumerable dependenc /// /// Returns a plugin context based on its id. /// - /// The id of the plugin. + /// The id of the plugin. + /// The plugin context. + public IPluginContext GetPlugin(string pluginId) + { + return _dictionary.Values + .Where + ( + x => x.PluginContext != null && + x.PluginContext.PluginId.ToString().Equals(pluginId) + ) + .Select(x => x.PluginContext) + .FirstOrDefault(); + } + + /// + /// Returns a plugin context based on its id. + /// + /// The type of the plugin. /// The plugin context. - public IPluginContext GetPlugin(string id) + public IPluginContext GetPlugin(Type plugin) { - return Dictionary.Values + return _dictionary.Values .Where ( x => x.PluginContext != null && - x.PluginContext.PluginId.Equals(id, StringComparison.OrdinalIgnoreCase) + x.PluginClass.Equals(plugin) ) .Select(x => x.PluginContext) .FirstOrDefault(); } + /// + /// Returns all plugins that have associated applications. + /// + /// The application context to filter plugins. + /// An enumerable collection of plugin contexts with applications. + public IEnumerable GetPlugins(IApplicationContext applicationContext) + { + return _dictionary.Values + .Where(x => x.ApplicationTypes != null) + .Where(x => x.ApplicationTypes.Select(x => _componentHub.ApplicationManager.GetApplications(x)) + .SelectMany(x => x) + .Where(x => x.ApplicationId == applicationContext.ApplicationId) + .Any()) + .Select(x => x.PluginContext); + } + + /// + /// Returns all ApplicationContext instances associated with a plugin. + /// + /// The context of the plugin. + /// A collection of ApplicationContext instances. + public IEnumerable GetAssociatedApplications(IPluginContext pluginContext) + { + var pluginItem = GetPluginItem(pluginContext); + + if (pluginItem == null) + { + return []; + } + + return pluginItem.ApplicationTypes? + .Select(x => _componentHub.ApplicationManager.GetApplications(x)) + .SelectMany(x => x) + .Where(x => x != null) ?? []; + } + + /// /// Returns a plugin item based on the context. /// @@ -389,15 +502,15 @@ public IPluginContext GetPlugin(string id) /// The plugin item or null. private PluginItem GetPluginItem(IPluginContext pluginContext) { - var pluginId = pluginContext?.PluginId?.ToLower(); + var pluginId = pluginContext?.PluginId; - if (pluginId == null || !Dictionary.ContainsKey(pluginId)) + if (pluginId == null || !_dictionary.TryGetValue(pluginId, out PluginItem value)) { - HttpServerContext.Log.Warning + _httpServerContext.Log.Warning ( - InternationalizationManager.I18N + I18N.Translate ( - "webexpress:pluginmanager.notavailable", + "webexpress.webcore:pluginmanager.notavailable", pluginId ) ); @@ -405,7 +518,7 @@ private PluginItem GetPluginItem(IPluginContext pluginContext) return null; } - return Dictionary[pluginId]; + return value; } /// @@ -422,36 +535,30 @@ internal void Boot(IPluginContext pluginContext) return; } - // initialize plugin - pluginItem.Plugin.Initialization(pluginItem.PluginContext); - HttpServerContext.Log.Debug - ( - InternationalizationManager.I18N - ( - "webexpress:pluginmanager.plugin.initialization", - pluginItem.PluginContext.PluginId - ) - ); + if (pluginItem.Plugin == null) + { + return; + } // run plugin concurrently Task.Run(() => { - HttpServerContext.Log.Debug + _httpServerContext.Log.Debug ( - InternationalizationManager.I18N + I18N.Translate ( - "webexpress:pluginmanager.plugin.processing.start", + "webexpress.webcore:pluginmanager.plugin.processing.start", pluginItem.PluginContext.PluginId ) ); pluginItem.Plugin.Run(); - HttpServerContext.Log.Debug + _httpServerContext.Log.Debug ( - InternationalizationManager.I18N + I18N.Translate ( - "webexpress:pluginmanager.plugin.processing.end", + "webexpress.webcore:pluginmanager.plugin.processing.end", pluginItem.PluginContext.PluginId ) ); @@ -476,7 +583,7 @@ internal void Boot(IEnumerable contexts) /// Shut down the plugin. /// /// The context of the plugin to shut down. - public void ShutDown(IPluginContext pluginContext) + internal void ShutDown(IPluginContext pluginContext) { var plugin = GetPluginItem(pluginContext); @@ -488,7 +595,7 @@ public void ShutDown(IPluginContext pluginContext) /// Shut down the plugins. /// /// A list of contexts of plugins to shut down. - public void ShutDown(IEnumerable contexts) + internal void ShutDown(IEnumerable contexts) { foreach (var context in contexts) { @@ -499,7 +606,7 @@ public void ShutDown(IEnumerable contexts) /// /// Raises the AddPlugin event. /// - /// The plugin context. + /// The plugin context. private void OnAddPlugin(IPluginContext pluginContext) { AddPlugin?.Invoke(this, pluginContext); @@ -508,7 +615,7 @@ private void OnAddPlugin(IPluginContext pluginContext) /// /// Raises the RemovePlugin event. /// - /// The plugin context. + /// The plugin context. private void OnRemovePlugin(IPluginContext pluginContext) { RemovePlugin?.Invoke(this, pluginContext); @@ -517,65 +624,62 @@ private void OnRemovePlugin(IPluginContext pluginContext) /// /// Output of the loaded plugins to the log. /// - private void Logging() + private void Log() { - using var frame = new LogFrameSimple(HttpServerContext.Log); + using var frame = new LogFrameSimple(_httpServerContext.Log); var list = new List(); - HttpServerContext.Log.Info + _httpServerContext.Log.Info ( - InternationalizationManager.I18N + I18N.Translate ( - "webexpress:pluginmanager.pluginmanager.label" + "webexpress.webcore:pluginmanager.pluginmanager.label" ) ); - list.AddRange(Dictionary + list.AddRange(_dictionary .Where ( x => x.Value.PluginClass.Assembly - .GetCustomAttribute(typeof(SystemPluginAttribute)) != null + .GetCustomAttribute() != null ) - .Select(x => InternationalizationManager.I18N + .Select(x => string.Empty.PadRight(2) + I18N.Translate ( - "webexpress:pluginmanager.pluginmanager.system", + "webexpress.webcore:pluginmanager.pluginmanager.system", x.Key )) ); - list.AddRange(Dictionary + list.AddRange(_dictionary .Where ( x => x.Value.PluginClass.Assembly - .GetCustomAttribute(typeof(SystemPluginAttribute)) == null + .GetCustomAttribute() == null ) - .Select(x => InternationalizationManager.I18N + .Select(x => string.Empty.PadRight(2) + I18N.Translate ( - "webexpress:pluginmanager.pluginmanager.custom", + "webexpress.webcore:pluginmanager.pluginmanager.custom", x.Key )) ); - list.AddRange(UnfulfilledDependencies - .Select(x => InternationalizationManager.I18N + list.AddRange(_unfulfilledDependencies + .Select(x => string.Empty.PadRight(2) + I18N.Translate ( - "webexpress:pluginmanager.pluginmanager.unfulfilleddependencies", + "webexpress.webcore:pluginmanager.pluginmanager.unfulfilleddependencies", x.Key )) ); foreach (var item in list) { - HttpServerContext.Log.Info(string.Join(Environment.NewLine, item)); + _httpServerContext.Log.Info(string.Join(Environment.NewLine, item)); } } /// - /// Information about the component is collected and prepared for output in the log. + /// Release of unmanaged resources reserved during use. /// - /// The context of the plugin. - /// A list of log entries. - /// The shaft deep. - public void PrepareForLog(IPluginContext pluginContext, IList output, int deep) + public void Dispose() { } } diff --git a/src/WebExpress.WebCore/WebResource/IResource.cs b/src/WebExpress.WebCore/WebResource/IResource.cs index ebf21b3..583e12d 100644 --- a/src/WebExpress.WebCore/WebResource/IResource.cs +++ b/src/WebExpress.WebCore/WebResource/IResource.cs @@ -1,57 +1,18 @@ -using WebExpress.WebCore.Internationalization; -using WebExpress.WebCore.WebApplication; +using WebExpress.WebCore.WebEndpoint; using WebExpress.WebCore.WebMessage; -using WebExpress.WebCore.WebModule; namespace WebExpress.WebCore.WebResource { - public interface IResource : II18N + /// + /// Defines the contract for a resource component. + /// + public interface IResource : IEndpoint { - /// - /// Returns the resource Id. - /// - string Id { get; } - - /// - /// Returns the context of the application. - /// - IApplicationContext ApplicationContext { get; } - - /// - /// Returns the context of the module. - /// - IModuleContext ModuleContext { get; } - - /// - /// Returns the module context where the resource exists. - /// - IResourceContext ResourceContext { get; } - - /// - /// Initialization - /// - /// The context of the resource. - void Initialization(IResourceContext resourceContext); - - /// - /// Preprocessing of the resource. - /// - /// The request. - void PreProcess(Request request); - /// /// Processing of the resource. /// /// The request. /// The response. Response Process(Request request); - - /// - /// Post-processing of the resource. - /// - /// The request. - /// The response. - /// The response. - Response PostProcess(Request request, Response response); } } diff --git a/src/WebExpress.WebCore/WebResource/IResourceContext.cs b/src/WebExpress.WebCore/WebResource/IResourceContext.cs index a593cbd..c8bed1c 100644 --- a/src/WebExpress.WebCore/WebResource/IResourceContext.cs +++ b/src/WebExpress.WebCore/WebResource/IResourceContext.cs @@ -1,69 +1,12 @@ -using System.Collections.Generic; -using WebExpress.WebCore.WebApplication; -using WebExpress.WebCore.WebCondition; -using WebExpress.WebCore.WebModule; -using WebExpress.WebCore.WebPlugin; -using WebExpress.WebCore.WebUri; +using WebExpress.WebCore.WebEndpoint; namespace WebExpress.WebCore.WebResource { - public interface IResourceContext + /// + /// Defines the context for a resource, providing access to various related contexts and properties. + /// + public interface IResourceContext : IEndpointContext { - /// - /// Returns the associated plugin context. - /// - IPluginContext PluginContext { get; } - /// - /// Returns the associated application context. - /// - IApplicationContext ApplicationContext { get; } - - /// - /// Returns the corresponding module context. - /// - IModuleContext ModuleContext { get; } - - /// - /// Returns the scope names that provides the resource. The scope name - /// is a string with a name (e.g. global, admin), which can be used by elements to - /// determine whether content and how content should be displayed. - /// - IEnumerable Scopes { get; } - - /// - /// Provides the conditions that must be met for the resource to be active. - /// - IEnumerable Conditions { get; } - - /// - /// Returns the resource id. - /// - string ResourceId { get; } - - /// - /// Returns the resource title. - /// - string ResourceTitle { get; } - - /// - /// Returns the parent or null if not used. - /// - IResourceContext ParentContext { get; } - - /// - /// Determines whether the resource is created once and reused each time it is called. - /// - bool Cache { get; } - - /// - /// Returns the context path. - /// - UriResource ContextPath { get; } - - /// - /// Returns the uri. - /// - UriResource Uri { get; } } } diff --git a/src/WebExpress.WebCore/WebResource/IResourceManager.cs b/src/WebExpress.WebCore/WebResource/IResourceManager.cs new file mode 100644 index 0000000..8e19486 --- /dev/null +++ b/src/WebExpress.WebCore/WebResource/IResourceManager.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections.Generic; +using WebExpress.WebCore.WebApplication; +using WebExpress.WebCore.WebComponent; +using WebExpress.WebCore.WebPlugin; + +namespace WebExpress.WebCore.WebResource +{ + /// + /// The resource manager manages resources elements, which can be called with a URI (Uniform Resource Identifier). + /// + public interface IResourceManager : IComponentManager + { + /// + /// An event that fires when an resource is added. + /// + event EventHandler AddResource; + + /// + /// An event that fires when an resource is removed. + /// + event EventHandler RemoveResource; + + /// + /// Returns all resource contexts. + /// + IEnumerable Resources { get; } + + /// + /// Returns an enumeration of all containing resource contexts of a plugin. + /// + /// A context of a plugin whose resources are to be registered. + /// An enumeration of resource contexts. + IEnumerable GetResorces(IPluginContext pluginContext); + + /// + /// Returns an enumeration of resource contextes. + /// + /// The resource type. + /// An enumeration of resource contextes. + IEnumerable GetResorces() where T : IResource; + + /// + /// Returns an enumeration of resource contextes. + /// + /// The resource type. + /// An enumeration of resource contextes. + IEnumerable GetResorces(Type resourceType); + + /// + /// Returns an enumeration of resource contextes. + /// + /// The resource type. + /// The context of the application. + /// An enumeration of resource contextes. + IEnumerable GetResorces(Type resourceType, IApplicationContext applicationContext); + + /// + /// Returns an enumeration of resource contextes. + /// + /// The resource type. + /// The context of the application. + /// An enumeration of resource contextes. + IEnumerable GetResorces(IApplicationContext applicationContext) where T : IResource; + + /// + /// Returns the resource context. + /// + /// The context of the application. + /// The resource id. + /// An resource context or null. + IResourceContext GetResorce(IApplicationContext applicationContext, string resourceId); + + /// + /// Returns the resource context. + /// + /// The application id. + /// The resource id. + /// An resource context or null. + IResourceContext GetResorce(string applicationId, string resourceId); + } +} diff --git a/src/WebExpress.WebCore/WebResource/Model/ResourceDictionary.cs b/src/WebExpress.WebCore/WebResource/Model/ResourceDictionary.cs new file mode 100644 index 0000000..d0ca85d --- /dev/null +++ b/src/WebExpress.WebCore/WebResource/Model/ResourceDictionary.cs @@ -0,0 +1,133 @@ +using System; +using System.Collections.Generic; +using WebExpress.WebCore.WebApplication; +using WebExpress.WebCore.WebPlugin; + +namespace WebExpress.WebCore.WebResource.Model +{ + /// + /// Represents a dictionary that maps plugin contexts to application contexts, resource types, and resource items. + /// key = plugin context + /// value = { key = resource type, value = ressource item } + /// + internal class ResourceDictionary : Dictionary>> + { + /// + /// Adds a resource item to the dictionary. + /// + /// The plugin context. + /// The application context. + /// The resource item. + /// True if the resource item was added successfully, false if an element with the same status code already exists. + public bool AddResourceItem(IPluginContext pluginContext, IApplicationContext applicationContext, ResourceItem resourceItem) + { + var type = resourceItem.ResourceClass; + + if (!typeof(IResource).IsAssignableFrom(type)) + { + return false; + } + + if (!ContainsKey(pluginContext)) + { + this[pluginContext] = []; + } + + var appContextDict = this[pluginContext]; + + if (!appContextDict.ContainsKey(applicationContext)) + { + appContextDict[applicationContext] = []; + } + + var resourceDict = appContextDict[applicationContext]; + + if (!resourceDict.ContainsKey(type)) + { + resourceDict[type] = resourceItem; + return true; + } + + return false; // item with the same resource class already exists + } + + /// + /// Removes a resource from the dictionary. + /// + /// The plugin context. + /// The application context. + public void RemoveResource(IPluginContext pluginContext, IApplicationContext applicationContext) + where TResource : IResource + { + var type = typeof(TResource); + + if (ContainsKey(pluginContext)) + { + var appContextDict = this[pluginContext]; + + if (appContextDict.ContainsKey(applicationContext)) + { + var resourceDict = appContextDict[applicationContext]; + + if (resourceDict.ContainsKey(type)) + { + resourceDict.Remove(type); + + if (resourceDict.Count == 0) + { + appContextDict.Remove(applicationContext); + + if (appContextDict.Count == 0) + { + Remove(pluginContext); + } + } + } + } + } + } + + /// + /// Returns the resource items from the dictionary. + /// + /// The type of resource. + /// The application context. + /// An IEnumerable of resource items + public IEnumerable GetResourceItems(IApplicationContext applicationContext) + where TResource : IResource + { + return GetResourceItems(applicationContext, typeof(TResource)); + } + + /// + /// Returns the resource items from the dictionary. + /// + /// The application context. + /// The type of resource. + /// An IEnumerable of resource items + public IEnumerable GetResourceItems(IApplicationContext applicationContext, Type resourceType) + { + if (!typeof(IResource).IsAssignableFrom(resourceType)) + { + return []; + } + + if (ContainsKey(applicationContext?.PluginContext)) + { + var appContextDict = this[applicationContext?.PluginContext]; + + if (appContextDict.ContainsKey(applicationContext)) + { + var resourceDict = appContextDict[applicationContext]; + + if (resourceDict.ContainsKey(resourceType)) + { + return [resourceDict[resourceType]]; + } + } + } + + return []; + } + } +} diff --git a/src/WebExpress.WebCore/WebResource/Model/ResourceItem.cs b/src/WebExpress.WebCore/WebResource/Model/ResourceItem.cs new file mode 100644 index 0000000..c036f88 --- /dev/null +++ b/src/WebExpress.WebCore/WebResource/Model/ResourceItem.cs @@ -0,0 +1,101 @@ +using System; +using System.Collections.Generic; +using WebExpress.WebCore.WebApplication; +using WebExpress.WebCore.WebComponent; +using WebExpress.WebCore.WebCondition; +using WebExpress.WebCore.WebEndpoint; +using WebExpress.WebCore.WebPlugin; +using WebExpress.WebCore.WebUri; + +namespace WebExpress.WebCore.WebResource.Model +{ + /// + /// A resource element that contains meta information about a resource. + /// + internal class ResourceItem : IDisposable + { + /// + /// Returns the endpoint id. + /// + public IComponentId EndpointId { get; internal set; } + + /// + /// Returns the context of the associated plugin. + /// + public IPluginContext PluginContext { get; internal set; } + + /// + /// Returns the application context. + /// + public IApplicationContext ApplicationContext { get; internal set; } + + /// + /// Returns or sets the type of resource. + /// + public Type ResourceClass { get; internal set; } + + /// + /// Returns or sets the instance of the resource, if the resource is cached, otherwise null. + /// + public IEndpoint Instance { get; internal set; } + + /// + /// Returns or sets the paths of the resource. + /// + public UriEndpoint ContextPath { get; internal set; } + + /// + /// Returns or sets the path segment. + /// + public IUriPathSegment PathSegment { get; internal set; } + + /// + /// Returns or sets whether all subpaths should be taken into sitemap. + /// + public bool IncludeSubPaths { get; internal set; } + + /// + /// Returns the conditions that must be met for the resource to be active. + /// + public IEnumerable Conditions { get; internal set; } + + /// + /// Returns whether the resource is created once and reused each time it is called. + /// + public bool Cache { get; internal set; } + + /// + /// Returns the attributes associated with the page. + /// + public IEnumerable Attributes { get; internal set; } + + /// + /// Returns the resource context. + /// + public IResourceContext ResourceContext { get; internal set; } + + /// + /// Initializes a new instance of the class. + /// + /// The resource manager. + internal ResourceItem(IResourceManager resourceManager) + { + } + + /// + /// Performs application-specific tasks related to sharing, returning, or resetting unmanaged resources. + /// + public void Dispose() + { + } + + /// + /// Convert the resource element to a string. + /// + /// The resource element in its string representation. + public override string ToString() + { + return $"Resource: '{ResourceContext?.EndpointId}'"; + } + } +} diff --git a/src/WebExpress.WebCore/WebResource/Resource.cs b/src/WebExpress.WebCore/WebResource/Resource.cs index 87e7a6f..9977312 100644 --- a/src/WebExpress.WebCore/WebResource/Resource.cs +++ b/src/WebExpress.WebCore/WebResource/Resource.cs @@ -1,60 +1,24 @@ -using System.Globalization; -using WebExpress.WebCore.WebApplication; -using WebExpress.WebCore.WebMessage; -using WebExpress.WebCore.WebModule; +using WebExpress.WebCore.WebMessage; namespace WebExpress.WebCore.WebResource { + /// + /// Represents a resource in the web application. + /// public abstract class Resource : IResource { /// - /// Returns the resource id. - /// - public string Id { get; internal set; } - - /// - /// Returns the context of the application. - /// - public IApplicationContext ApplicationContext { get; internal set; } - - /// - /// Returns the context of the module. - /// - public IModuleContext ModuleContext { get; internal set; } - - /// - /// Returns the module context where the resource exists. + /// Returns the resource context where the resource exists. /// public IResourceContext ResourceContext { get; private set; } /// - /// Provides the culture. - /// - public CultureInfo Culture { get; set; } - - /// - /// Constructor - /// - public Resource() - { - } - - /// - /// Initialization - /// + /// Initializes a new instance of the class. /// The context of the resource. - public virtual void Initialization(IResourceContext resourceContext) - { - ResourceContext = resourceContext; - } - - /// - /// Preprocessing of the resource. /// - /// The request. - public virtual void PreProcess(Request request) + public Resource(IResourceContext resourceContext) { - return; + ResourceContext = resourceContext; } /// @@ -65,14 +29,11 @@ public virtual void PreProcess(Request request) public abstract Response Process(Request request); /// - /// Post-processing of the resource. + /// Performs application-specific tasks related to sharing, returning, or resetting unmanaged resources. /// - /// The request. - /// The response. - /// The response. - public virtual Response PostProcess(Request request, Response response) + public virtual void Dispose() { - return response; + } } } diff --git a/src/WebExpress.WebCore/WebResource/ResourceAsset.cs b/src/WebExpress.WebCore/WebResource/ResourceAsset.cs index 336cad6..0bc86b9 100644 --- a/src/WebExpress.WebCore/WebResource/ResourceAsset.cs +++ b/src/WebExpress.WebCore/WebResource/ResourceAsset.cs @@ -23,21 +23,13 @@ public class ResourceAsset : ResourceBinary public string AssetDirectory { get; protected set; } /// - /// Constructor + /// Initializes a new instance of the class. /// - public ResourceAsset() + /// The resource context. + public ResourceAsset(IResourceContext resourceContext) + : base(resourceContext) { Gard = new object(); - } - - /// - /// Initialization - /// - /// The context. - public override void Initialization(IResourceContext context) - { - base.Initialization(context); - AssetDirectory = ResourceContext.PluginContext.Assembly.GetName().Name; } @@ -53,8 +45,7 @@ public override Response Process(Request request) var assembly = ResourceContext.PluginContext.Assembly; var buf = assembly.GetManifestResourceNames().ToList(); var resources = assembly.GetManifestResourceNames().Where(x => x.StartsWith(AssetDirectory, System.StringComparison.OrdinalIgnoreCase)); - var contextPath = ResourceContext.ContextPath; - var url = request.Uri.ExtendedPath.ToString(); + var url = request.Uri.ToString(); var fileName = Path.GetFileName(url); var file = string.Join('.', AssetDirectory.Trim('.'), "assets", url.Replace("/", ".").Trim('.')); @@ -126,9 +117,9 @@ public override Response Process(Request request) break; } - request.ServerContext.Log.Debug(InternationalizationManager.I18N + request.HttpServerContext.Log.Debug(I18N.Translate ( - "webexpress:resource.file", + "webexpress.webcore:resource.file", request.RemoteEndPoint, request.Uri )); @@ -139,9 +130,10 @@ public override Response Process(Request request) /// /// Reads the data of a specified resource. /// - /// The file. - /// The assembly. - /// The data. + /// The name of the resource file to read. + /// The assembly containing the resource. + /// A collection of resource names available in the assembly. + /// A byte array containing the resource data, or null if the resource is not found. private static byte[] GetData(string file, Assembly assembly, IEnumerable resources) { var item = resources.Where(x => x.Equals(file, System.StringComparison.OrdinalIgnoreCase)).FirstOrDefault(); @@ -156,5 +148,13 @@ private static byte[] GetData(string file, Assembly assembly, IEnumerable + /// Performs application-specific tasks related to sharing, returning, or resetting unmanaged resources. + /// + public override void Dispose() + { + + } } } \ No newline at end of file diff --git a/src/WebExpress.WebCore/WebResource/ResourceBinary.cs b/src/WebExpress.WebCore/WebResource/ResourceBinary.cs index c35de9c..396966a 100644 --- a/src/WebExpress.WebCore/WebResource/ResourceBinary.cs +++ b/src/WebExpress.WebCore/WebResource/ResourceBinary.cs @@ -13,9 +13,11 @@ public abstract class ResourceBinary : Resource public byte[] Data { get; set; } /// - /// Constructor + /// Initializes a new instance of the class. /// - public ResourceBinary() + /// The resource context. + public ResourceBinary(IResourceContext resourceContext) + : base(resourceContext) { } diff --git a/src/WebExpress.WebCore/WebResource/ResourceContext.cs b/src/WebExpress.WebCore/WebResource/ResourceContext.cs index d5fd449..09249e7 100644 --- a/src/WebExpress.WebCore/WebResource/ResourceContext.cs +++ b/src/WebExpress.WebCore/WebResource/ResourceContext.cs @@ -1,103 +1,78 @@ using System; using System.Collections.Generic; -using System.Linq; using WebExpress.WebCore.WebApplication; using WebExpress.WebCore.WebComponent; using WebExpress.WebCore.WebCondition; -using WebExpress.WebCore.WebModule; +using WebExpress.WebCore.WebEndpoint; using WebExpress.WebCore.WebPlugin; using WebExpress.WebCore.WebUri; namespace WebExpress.WebCore.WebResource { + /// + /// Represents the context of a resource. + /// public class ResourceContext : IResourceContext { /// - /// Returns the associated plugin context. + /// Returns the resource id. /// - public IPluginContext PluginContext { get; private set; } + public IComponentId EndpointId { get; internal set; } /// - /// Returns the associated application context. + /// Returns the associated plugin context. /// - public IApplicationContext ApplicationContext => ModuleContext?.ApplicationContext; + public IPluginContext PluginContext { get; internal set; } /// - /// Returns the corresponding module context. + /// Returns the corresponding application context. /// - public IModuleContext ModuleContext { get; private set; } + public IApplicationContext ApplicationContext { get; internal set; } /// - /// Returns the scope names that provides the resource. The scope name - /// is a string with a name (e.g. global, admin), which can be used by elements to - /// determine whether content and how content should be displayed. + /// Returns the conditions that must be met for the resource to be active. /// - public IEnumerable Scopes { get; internal set; } + public IEnumerable Conditions { get; internal set; } = []; /// - /// Returns the conditions that must be met for the resource to be active. + /// Returns whether the resource is created once and reused each time it is called. /// - public IEnumerable Conditions { get; internal set; } = new List(); + public bool Cache { get; internal set; } /// - /// Returns the resource id. + /// Returns or sets whether all subpaths should be taken into sitemap. /// - public string ResourceId { get; internal set; } + public bool IncludeSubPaths { get; internal set; } /// - /// Returns the resource title. + /// Returns the context path. /// - public string ResourceTitle { get; internal set; } + public UriEndpoint ContextPath { get; internal set; } /// - /// Returns the parent or null if not used. + /// Returns the internal routing path for the endpoint. /// - public IResourceContext ParentContext => ComponentManager.ResourceManager.Resources - .Where(x => !string.IsNullOrWhiteSpace(ResourceItem.ParentId)) - .Where(x => x.ResourceId.Equals(ResourceItem.ParentId, StringComparison.OrdinalIgnoreCase)) - .Where(x => x.ModuleContext.ApplicationContext == ModuleContext.ApplicationContext) - .FirstOrDefault(); + public IRoute Route { get; internal set; } /// - /// Returns whether the resource is created once and reused each time it is called. + /// Returns the attributes associated with the page. /// - public bool Cache { get; internal set; } + public IEnumerable Attributes { get; internal set; } /// - /// Returns the context path. + /// Initializes a new instance of the class with the specified endpoint manager, parent type, context path, and path segment. /// - public UriResource ContextPath + public ResourceContext() { - get - { - var parentContext = ParentContext; - if (parentContext != null) - { - return UriResource.Combine(ParentContext?.Uri, ResourceItem.ContextPath); - } - - return UriResource.Combine(ModuleContext.ContextPath, ResourceItem.ContextPath); - } } /// - /// Returns the uri. + /// Returns a string that represents the current object. /// - public UriResource Uri => ContextPath.Append(ResourceItem.PathSegment); - - /// - /// Constructor - /// - /// The module context. - internal ResourceContext(IModuleContext moduleContext) + /// A string that represents the current object. + public override string ToString() { - PluginContext = moduleContext?.PluginContext; - ModuleContext = moduleContext; + return $"Resource: {EndpointId}"; } - - /// - /// Returns or sets the resource item. - /// - internal ResourceItem ResourceItem { get; set; } } } diff --git a/src/WebExpress.WebCore/WebResource/ResourceDictionary.cs b/src/WebExpress.WebCore/WebResource/ResourceDictionary.cs deleted file mode 100644 index fb68497..0000000 --- a/src/WebExpress.WebCore/WebResource/ResourceDictionary.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System.Collections.Generic; -using WebExpress.WebCore.WebPlugin; - -namespace WebExpress.WebCore.WebResource -{ - /// - /// key = plugin context - /// value = { key = resource id, value = ressource item } - /// - internal class ResourceDictionary : Dictionary> - { - } -} diff --git a/src/WebExpress.WebCore/WebResource/ResourceFile.cs b/src/WebExpress.WebCore/WebResource/ResourceFile.cs index f293aa4..c43d0cc 100644 --- a/src/WebExpress.WebCore/WebResource/ResourceFile.cs +++ b/src/WebExpress.WebCore/WebResource/ResourceFile.cs @@ -19,22 +19,15 @@ public class ResourceFile : ResourceBinary public string RootDirectory { get; protected set; } /// - /// Constructor + /// Initializes a new instance of the class. /// - public ResourceFile() + /// The resource context. + public ResourceFile(IResourceContext resourceContext) + : base(resourceContext) { Gard = new object(); } - /// - /// Initialization - /// - /// The context. - public override void Initialization(IResourceContext context) - { - base.Initialization(context); - } - /// /// Processing of the resource. /// @@ -44,8 +37,7 @@ public override Response Process(Request request) { lock (Gard) { - var contextPath = ResourceContext.ContextPath; - var url = request.Uri.ToString()[contextPath.ToString().Length..]; + var url = request.Uri.ToString()[ResourceContext.Route.ToString().Length..]; var path = System.IO.Path.GetFullPath(RootDirectory + url); @@ -117,7 +109,7 @@ public override Response Process(Request request) break; } - request.ServerContext.Log.Debug(InternationalizationManager.I18N("webexpress:resource.file", request.RemoteEndPoint, request.Uri)); + request.HttpServerContext.Log.Debug(I18N.Translate("webexpress.webcore:resource.file", request.RemoteEndPoint, request.Uri)); return response; } diff --git a/src/WebExpress.WebCore/WebResource/ResourceItem.cs b/src/WebExpress.WebCore/WebResource/ResourceItem.cs deleted file mode 100644 index ae43196..0000000 --- a/src/WebExpress.WebCore/WebResource/ResourceItem.cs +++ /dev/null @@ -1,226 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text.RegularExpressions; -using WebExpress.WebCore.Internationalization; -using WebExpress.WebCore.WebCondition; -using WebExpress.WebCore.WebModule; -using WebExpress.WebCore.WebUri; - -namespace WebExpress.WebCore.WebResource -{ - /// - /// A resource element that contains meta information about a resource. - /// - public class ResourceItem : IDisposable - { - /// - /// An event that fires when an ressource is added. - /// - public event EventHandler AddResource; - - /// - /// An event that fires when an resource is removed. - /// - public event EventHandler RemoveResource; - - /// - /// Returns or sets the resource id. - /// - public string ResourceId { get; set; } - - /// - /// Returns or sets the resource title. - /// - public string Title { get; set; } - - /// - /// Returns or sets the parent id. - /// - public string ParentId { get; set; } - - /// - /// Returns or sets the type of resource. - /// - public Type ResourceClass { get; set; } - - /// - /// Returns or sets the instance of the resource, if the resource is cached, otherwise null. - /// - public IResource Instance { get; set; } - - /// - /// Returns or sets the module id. - /// - public string ModuleId { get; set; } - - /// - /// Returns the scope names that provides the resource. The scope name - /// is a string with a name (e.g. global, admin), which can be used by elements to - /// determine whether content and how content should be displayed. - /// - public IReadOnlyList Scopes { get; set; } - - /// - /// Returns or sets the paths of the resource. - /// - public UriResource ContextPath { get; set; } - - /// - /// Returns or sets the path segment. - /// - public IUriPathSegment PathSegment { get; internal set; } - - /// - /// Returns or sets whether all subpaths should be taken into sitemap. - /// - public bool IncludeSubPaths { get; set; } - - /// - /// Returns the conditions that must be met for the resource to be active. - /// - public ICollection Conditions { get; set; } - - /// - /// Returns whether the resource is created once and reused each time it is called. - /// - public bool Cache { get; set; } - - /// - /// Returns whether it is a optional resource. - /// - public bool Optional { get; set; } - - /// - /// Returns the log to write status messages to the console and to a log file. - /// - public Log Log { get; internal set; } - - /// - /// Returns the directory where the module instances are listed. - /// - private IDictionary Dictionary { get; } - = new Dictionary(); - - /// - /// Returns the associated module contexts. - /// - public IEnumerable ModuleContexts => Dictionary.Keys; - - /// - /// Returns the resource contexts. - /// - public IEnumerable ResourceContexts => Dictionary.Values; - - /// - /// Adds an module assignment - /// - /// The context of the module. - public void AddModule(IModuleContext moduleContext) - { - // only if no instance has been created yet - if (Dictionary.ContainsKey(moduleContext)) - { - Log.Warning(message: InternationalizationManager.I18N("webexpress:resourcemanager.addresource.duplicate", ResourceId, moduleContext.ModuleId)); - - return; - } - - // create context - var resourceContext = new ResourceContext(moduleContext) - { - Scopes = Scopes, - Conditions = Conditions, - ResourceId = ResourceId, - ResourceTitle = Title, - Cache = Cache, - ResourceItem = this - }; - - if - ( - !Optional || - moduleContext.ApplicationContext.Options.Contains($"{ModuleId.ToLower()}.{ResourceId.ToLower()}") || - moduleContext.ApplicationContext.Options.Contains($"{ModuleId.ToLower()}.*") || - moduleContext.ApplicationContext.Options.Where(x => Regex.Match($"{ModuleId.ToLower()}.{ResourceId.ToLower()}", x).Success).Any() - ) - { - Dictionary.Add(moduleContext, resourceContext); - OnAddResource(resourceContext); - } - } - - /// - /// Remove an module assignment - /// - /// The context of the module. - public void DetachModule(IModuleContext moduleContext) - { - // not an assignment has been created yet - if (!Dictionary.ContainsKey(moduleContext)) - { - return; - } - - foreach (var resourceContext in Dictionary.Values) - { - OnRemoveResource(resourceContext); - } - - Dictionary.Remove(moduleContext); - } - - /// - /// Checks whether a module context is already assigned to the item. - /// - /// The module context. - /// True a mapping exists, false otherwise. - public bool IsAssociatedWithModule(IModuleContext moduleContext) - { - return Dictionary.ContainsKey(moduleContext); - } - - /// - /// Raises the AddResource event. - /// - /// The resource context. - private void OnAddResource(IResourceContext resourceContext) - { - AddResource?.Invoke(this, resourceContext); - } - - /// - /// Raises the RemoveResource event. - /// - /// The resource context. - private void OnRemoveResource(IResourceContext resourceContext) - { - RemoveResource?.Invoke(this, resourceContext); - } - - /// - /// Performs application-specific tasks related to sharing, returning, or resetting unmanaged resources. - /// - public void Dispose() - { - foreach (Delegate d in AddResource.GetInvocationList()) - { - AddResource -= (EventHandler)d; - } - - foreach (Delegate d in RemoveResource.GetInvocationList()) - { - RemoveResource -= (EventHandler)d; - } - } - - /// - /// Convert the resource element to a string. - /// - /// The resource element in its string representation. - public override string ToString() - { - return $"Resource '{ResourceId}'"; - } - } -} diff --git a/src/WebExpress.WebCore/WebResource/ResourceManager.cs b/src/WebExpress.WebCore/WebResource/ResourceManager.cs index affa6c1..6ddd4ae 100644 --- a/src/WebExpress.WebCore/WebResource/ResourceManager.cs +++ b/src/WebExpress.WebCore/WebResource/ResourceManager.cs @@ -1,23 +1,28 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using WebExpress.WebCore.Internationalization; +using WebExpress.WebCore.WebApplication; using WebExpress.WebCore.WebAttribute; using WebExpress.WebCore.WebComponent; using WebExpress.WebCore.WebCondition; -using WebExpress.WebCore.WebModule; +using WebExpress.WebCore.WebEndpoint; using WebExpress.WebCore.WebPlugin; -using WebExpress.WebCore.WebScope; +using WebExpress.WebCore.WebResource.Model; using WebExpress.WebCore.WebStatusPage; -using WebExpress.WebCore.WebUri; namespace WebExpress.WebCore.WebResource { /// /// The resource manager manages WebExpress elements, which can be called with a URI (Uniform Resource Identifier). /// - public sealed class ResourceManager : IComponentPlugin, ISystemComponent + public sealed class ResourceManager : IResourceManager, ISystemComponent { + private readonly IComponentHub _componentHub; + private readonly IHttpServerContext _httpServerContext; + private readonly ResourceDictionary _dictionary = []; + /// /// An event that fires when an resource is added. /// @@ -28,134 +33,121 @@ public sealed class ResourceManager : IComponentPlugin, ISystemComponent /// public event EventHandler RemoveResource; - /// - /// Returns the reference to the context of the host. - /// - public IHttpServerContext HttpServerContext { get; private set; } - - /// - /// Returns the directory where the resources are listed. - /// - private ResourceDictionary Dictionary { get; } = new ResourceDictionary(); - - /// - /// Returns all resource items. - /// - internal IEnumerable ResourceItems => Dictionary.Values.SelectMany(x => x.Values); - /// /// Returns all resource contexts. /// - internal IEnumerable Resources => Dictionary.Values + public IEnumerable Resources => _dictionary.Values + .SelectMany(x => x.Values) .SelectMany(x => x.Values) - .SelectMany(x => x.ResourceContexts); + .Select(x => x.ResourceContext); /// - /// Constructor + /// Initializes a new instance of the class. /// - internal ResourceManager() + /// The component hub. + /// The reference to the context of the host. + [SuppressMessage("CodeQuality", "IDE0051:Remove unused private members", Justification = "Used via Reflection.")] + private ResourceManager(IComponentHub componentHub, IHttpServerContext httpServerContext) { - ComponentManager.PluginManager.AddPlugin += (sender, pluginContext) => - { - Register(pluginContext); - }; + _componentHub = componentHub; - ComponentManager.PluginManager.RemovePlugin += (sender, pluginContext) => - { - Remove(pluginContext); - }; + _componentHub.PluginManager.AddPlugin += OnAddPlugin; + _componentHub.PluginManager.RemovePlugin += OnRemovePlugin; + _componentHub.ApplicationManager.AddApplication += OnAddApplication; + _componentHub.ApplicationManager.RemoveApplication += OnRemoveApplication; - ComponentManager.ModuleManager.AddModule += (sender, moduleContext) => + var endpointtRegistration = new EndpointRegistration() { - AssignToModule(moduleContext); - }; + EndpointResolver = (type, applicationContext) => applicationContext != null ? GetResorces(type, applicationContext) : GetResorces(type), + EndpointsResolver = () => Resources, + HandleRequest = (request, endpointContext) => + { + var resourceContext = endpointContext as IResourceContext; + var resource = CreateResourceInstance(resourceContext); - ComponentManager.ModuleManager.RemoveModule += (sender, moduleContext) => - { - DetachFromModule(moduleContext); + return resource.Process(request); + } }; + + AddResource += (sender, e) => endpointtRegistration.AddEndpoint?.Invoke(sender, e); + RemoveResource += (sender, e) => endpointtRegistration.RemoveEndpoint?.Invoke(sender, e); + + _componentHub.EndpointManager.Register(endpointtRegistration); + + _httpServerContext = httpServerContext; + + _httpServerContext.Log.Debug + ( + I18N.Translate("webexpress.webcore:resourcemanager.initialization") + ); } /// - /// Initialization + /// Discovers and binds resources to an application. /// - /// The reference to the context of the host. - public void Initialization(IHttpServerContext context) + /// The context of the plugin whose resources are to be associated. + private void Register(IPluginContext pluginContext) { - HttpServerContext = context; + if (_dictionary.ContainsKey(pluginContext)) + { + return; + } - HttpServerContext.Log.Debug - ( - InternationalizationManager.I18N("webexpress:resourcemanager.initialization") - ); + Register(pluginContext, _componentHub.ApplicationManager.GetApplications(pluginContext)); } /// - /// Discovers and registers resources from the specified plugin. + /// Discovers and binds resources to an application. /// - /// A context of a plugin whose resources are to be registered. - public void Register(IPluginContext pluginContext) + /// The context of the application whose resources are to be associated. + private void Register(IApplicationContext applicationContext) { - if (Dictionary.ContainsKey(pluginContext)) + foreach (var pluginContext in _componentHub.PluginManager.GetPlugins(applicationContext)) { - return; + if (_dictionary.TryGetValue(pluginContext, out var appDict) && appDict.ContainsKey(applicationContext)) + { + continue; + } + + Register(pluginContext, [applicationContext]); } + } + /// + /// Registers resources for a given plugin and application context. + /// + /// The plugin context. + /// The application context (optional). + private void Register(IPluginContext pluginContext, IEnumerable applicationContexts) + { var assembly = pluginContext?.Assembly; - Dictionary.Add(pluginContext, new Dictionary()); - var dict = Dictionary[pluginContext]; - foreach (var resourceType in assembly.GetTypes() - .Where(x => x.IsClass == true && x.IsSealed && x.IsPublic) + .Where(x => x.IsClass && x.IsSealed && x.IsPublic) .Where(x => x.GetInterface(typeof(IResource).Name) != null) .Where(x => x.GetInterface(typeof(IStatusPage).Name) == null)) { var id = resourceType.FullName?.ToLower(); var segment = default(ISegmentAttribute); - var title = resourceType.Name; - var parent = default(string); var contextPath = string.Empty; var includeSubPaths = false; - var moduleId = string.Empty; - var scopes = new List(); var conditions = new List(); - var optional = false; var cache = false; + var attributes = resourceType.CustomAttributes + .Where(x => !x.AttributeType.GetInterfaces().Contains(typeof(IEndpointAttribute)) && + !x.AttributeType.GetInterfaces().Contains(typeof(IPageAttribute))); foreach (var customAttribute in resourceType.CustomAttributes - .Where(x => x.AttributeType.GetInterfaces().Contains(typeof(IResourceAttribute)))) + .Where(x => x.AttributeType.GetInterfaces().Contains(typeof(IEndpointAttribute)))) { - var buf = typeof(ModuleAttribute<>); - if (customAttribute.AttributeType.GetInterfaces().Contains(typeof(ISegmentAttribute))) { segment = resourceType.GetCustomAttributes(customAttribute.AttributeType, false).FirstOrDefault() as ISegmentAttribute; } - else if (customAttribute.AttributeType == typeof(TitleAttribute)) - { - title = customAttribute.ConstructorArguments.FirstOrDefault().Value?.ToString(); - } - else if (customAttribute.AttributeType.Name == typeof(ParentAttribute<>).Name && customAttribute.AttributeType.Namespace == typeof(ParentAttribute<>).Namespace) - { - parent = customAttribute.AttributeType.GenericTypeArguments.FirstOrDefault()?.FullName?.ToLower(); - } - else if (customAttribute.AttributeType == typeof(ContextPathAttribute)) - { - contextPath = customAttribute.ConstructorArguments.FirstOrDefault().Value?.ToString(); - } else if (customAttribute.AttributeType == typeof(IncludeSubPathsAttribute)) { includeSubPaths = Convert.ToBoolean(customAttribute.ConstructorArguments.FirstOrDefault().Value); } - else if (customAttribute.AttributeType.Name == typeof(ModuleAttribute<>).Name && customAttribute.AttributeType.Namespace == typeof(ModuleAttribute<>).Namespace) - { - moduleId = customAttribute.AttributeType.GenericTypeArguments.FirstOrDefault()?.FullName?.ToLower(); - } - else if (customAttribute.AttributeType.Name == typeof(ScopeAttribute<>).Name && customAttribute.AttributeType.Namespace == typeof(ScopeAttribute<>).Namespace) - { - scopes.Add(customAttribute.AttributeType.GenericTypeArguments.FirstOrDefault()?.FullName?.ToLower()); - } else if (customAttribute.AttributeType.Name == typeof(ConditionAttribute<>).Name && customAttribute.AttributeType.Namespace == typeof(ConditionAttribute<>).Namespace) { var condition = customAttribute.AttributeType.GenericTypeArguments.FirstOrDefault(); @@ -165,191 +157,249 @@ public void Register(IPluginContext pluginContext) { cache = true; } - else if (customAttribute.AttributeType == typeof(OptionalAttribute)) - { - optional = true; - } } - if (resourceType.GetInterfaces().Where(x => x == typeof(IScope)).Any()) + // assign the resource to existing applications + foreach (var applicationContext in applicationContexts) { - scopes.Add(resourceType.FullName?.ToLower()); - } - - if (string.IsNullOrEmpty(moduleId)) - { - // no module specified - HttpServerContext.Log.Warning + var prefix = applicationContext.ContextPath.Concat ( - InternationalizationManager.I18N - ( - "webexpress:resourcemanager.moduleless", - id - ) + applicationContext.PluginContext != pluginContext + ? pluginContext.PluginName.ToLower() + : "" ); - - continue; - } - - if (!dict.ContainsKey(id)) - { - var resourceItem = new ResourceItem() + var routePath = EndpointManager.CreateEndpointRoute(resourceType, prefix, segment); + var resourceContext = new ResourceContext() { - ResourceId = id, - Title = title, - ParentId = parent, - ResourceClass = resourceType, - ModuleId = moduleId, - Scopes = scopes, + EndpointId = new ComponentId(id), + PluginContext = pluginContext, + ApplicationContext = applicationContext, + Route = routePath, Cache = cache, Conditions = conditions, - ContextPath = new UriResource(contextPath), IncludeSubPaths = includeSubPaths, - PathSegment = segment.ToPathSegment(), - Optional = optional, - Log = HttpServerContext.Log + Attributes = attributes.Select(x => x.AttributeType) }; - resourceItem.AddResource += (s, e) => + var resourceItem = new ResourceItem(_componentHub.ResourceManager) { - OnAddResource(e); + EndpointId = new ComponentId(id), + PluginContext = pluginContext, + ApplicationContext = applicationContext, + ResourceContext = resourceContext, + ResourceClass = resourceType, + Cache = cache, + Conditions = conditions, + IncludeSubPaths = includeSubPaths, + Attributes = attributes.Select(x => x.AttributeType) }; - resourceItem.RemoveResource += (s, e) => + if (_dictionary.AddResourceItem(pluginContext, applicationContext, resourceItem)) { - OnRemoveResource(e); - }; - - dict.Add(id, resourceItem); - - HttpServerContext.Log.Debug - ( - InternationalizationManager.I18N - ( - "webexpress:resourcemanager.addresource", - id, - moduleId - ) - ); + OnAddResource(resourceItem.ResourceContext); + _httpServerContext?.Log.Debug( + I18N.Translate( + "webexpress.webcore:resourcemanager.addresource", + id, + applicationContext.ApplicationId + ) + ); + } } + } + } - // assign the resource to existing modules. - foreach (var moduleContext in ComponentManager.ModuleManager.GetModules(pluginContext, moduleId)) + /// + /// Removes all resources associated with the specified plugin context. + /// + /// The context of the plugin that contains the resources to remove. + internal void Remove(IPluginContext pluginContext) + { + if (pluginContext == null) + { + return; + } + + // the plugin has not been registered in the manager + if (_dictionary.TryGetValue(pluginContext, out var value)) + { + foreach (var resourceItem in value.Values + .SelectMany(x => x.Values)) { - AssignToModule(moduleContext); + OnRemoveResource(resourceItem.ResourceContext); + resourceItem.Dispose(); } + + _dictionary.Remove(pluginContext); } } /// - /// Discovers and registers resources from the specified plugin. + /// Removes all resources associated with the specified application context. /// - /// A list with plugin contexts that contain the resources. - public void Register(IEnumerable pluginContexts) + /// The context of the application that contains the resources to remove. + internal void Remove(IApplicationContext applicationContext) { - foreach (var pluginContext in pluginContexts) + if (applicationContext == null) + { + return; + } + + foreach (var pluginDict in _dictionary.Values) { - Register(pluginContext); + foreach (var appDict in pluginDict.Where(x => x.Key == applicationContext).Select(x => x.Value)) + { + foreach (var resourceItem in appDict.Values) + { + OnRemoveResource(resourceItem.ResourceContext); + resourceItem.Dispose(); + } + } + + pluginDict.Remove(applicationContext); } } /// - /// Assign existing resources to the module. + /// Returns an enumeration of all containing resource contexts of a plugin. /// - /// The context of the module. - private void AssignToModule(IModuleContext moduleContext) + /// A context of a plugin whose resources are to be registered. + /// An enumeration of resource contexts. + public IEnumerable GetResorces(IPluginContext pluginContext) { - foreach (var resourceItem in Dictionary.Values - .SelectMany(x => x.Values) - .Where(x => x.ModuleId.Equals(moduleContext?.ModuleId, StringComparison.OrdinalIgnoreCase)) - .Where(x => !x.IsAssociatedWithModule(moduleContext))) + if (_dictionary.TryGetValue(pluginContext, out var pluginResources)) { - resourceItem.AddModule(moduleContext); + return pluginResources + .SelectMany(x => x.Value) + .Select(x => x.Value.ResourceContext); } + + return []; } /// - /// Remove an existing modules to the application. + /// Returns an enumeration of resource contextes. /// - /// The context of the module. - private void DetachFromModule(IModuleContext moduleContext) + /// The resource type. + /// An enumeration of resource contextes. + public IEnumerable GetResorces() where T : IResource { - foreach (var resourceItem in Dictionary.Values - .SelectMany(x => x.Values) - .Where(x => !x.IsAssociatedWithModule(moduleContext))) - { - resourceItem.DetachModule(moduleContext); - } + return GetResorces(typeof(T)); } /// - /// Renturns an enumeration of all containing resource items of a plugin. + /// Returns an enumeration of resource contextes. /// - /// A context of a plugin whose resources are to be registered. - /// An enumeration of resource items. - internal IEnumerable GetResorceItems(IPluginContext pluginContext) + /// The resource type. + /// An enumeration of resource contextes. + public IEnumerable GetResorces(Type resourceType) { - if (!Dictionary.ContainsKey(pluginContext)) - { - return new List(); - } + return _dictionary.Values + .SelectMany(x => x.Values) + .SelectMany(x => x.Values) + .Where(x => x.ResourceClass.Equals(resourceType)) + .Select(x => x.ResourceContext); + } - return Dictionary[pluginContext].Values; + /// + /// Returns an enumeration of resource contextes. + /// + /// The resource type. + /// The context of the application. + /// An enumeration of resource contextes. + public IEnumerable GetResorces(Type resourceType, IApplicationContext applicationContext) + { + return _dictionary.Values + .SelectMany(x => x.Values) + .SelectMany(x => x.Values) + .Where(x => x.ResourceClass.Equals(resourceType)) + .Where(x => x.ResourceContext.ApplicationContext.Equals(applicationContext)) + .Select(x => x.ResourceContext); } /// - /// Renturns an enumeration of all containing resource contexts of a plugin. + /// Returns an enumeration of resource contextes. /// - /// A context of a plugin whose resources are to be registered. - /// An enumeration of resource contexts. - public IEnumerable GetResorces(IPluginContext pluginContext) + /// The resource type. + /// The context of the application. + /// An enumeration of resource contextes. + public IEnumerable GetResorces(IApplicationContext applicationContext) where T : IResource { - if (!Dictionary.ContainsKey(pluginContext)) - { - return new List(); - } + return _dictionary.Values + .SelectMany(x => x.Values) + .SelectMany(x => x.Values) + .Where(x => x.ResourceClass.Equals(typeof(T))) + .Where(x => x.ResourceContext.ApplicationContext.Equals(applicationContext)) + .Select(x => x.ResourceContext); + } + - return Dictionary[pluginContext].Values - .SelectMany(x => x.ResourceContexts); + /// + /// Returns the resource context. + /// + /// The context of the application. + /// The resource id. + /// An resource context or null. + public IResourceContext GetResorce(IApplicationContext applicationContext, string resourceId) + { + return _dictionary.Values + .SelectMany(x => x.Values) + .SelectMany(x => x.Values) + .Where(x => x.ResourceContext.ApplicationContext.Equals(applicationContext)) + .Where(x => x.ResourceContext.EndpointId.Equals(resourceId)) + .Select(x => x.ResourceContext) + .FirstOrDefault(); } /// - /// Renturns the resource context. + /// Returns the resource context. /// /// The application id. - /// The module id. /// The resource id. /// An resource context or null. - public IResourceContext GetResorces(string applicationId, string moduleId, string resourceId) + public IResourceContext GetResorce(string applicationId, string resourceId) { - return Dictionary.Values + return _dictionary.Values + .SelectMany(x => x.Values) .SelectMany(x => x.Values) - .SelectMany(x => x.ResourceContexts) - .Where(x => x.ModuleContext != null && x.ModuleContext.ApplicationContext != null) - .Where(x => x.ModuleContext.ApplicationContext.ApplicationId.Equals(applicationId, StringComparison.OrdinalIgnoreCase)) - .Where(x => x.ModuleContext.ModuleId.Equals(moduleId, StringComparison.OrdinalIgnoreCase)) - .Where(x => x.ResourceId.Equals(resourceId, StringComparison.OrdinalIgnoreCase)) + .Where(x => x.ResourceContext.ApplicationContext.ApplicationId.Equals(applicationId)) + .Where(x => x.ResourceContext.EndpointId.Equals(resourceId)) + .Select(x => x.ResourceContext) .FirstOrDefault(); } /// - /// Removes all resources associated with the specified plugin context. + /// Creates a new resource and returns it. If a resource already exists (through caching), the existing instance is returned. /// - /// The context of the plugin that contains the resources to remove. - public void Remove(IPluginContext pluginContext) + /// The context used for resource creation. + /// The created or cached resource. + private IResource CreateResourceInstance(IResourceContext resourceContext) { - // the plugin has not been registered in the manager - if (!Dictionary.ContainsKey(pluginContext)) - { - return; - } + var resourceItem = _dictionary.Values + .SelectMany(x => x.Values) + .SelectMany(x => x.Values) + .FirstOrDefault(x => x.ResourceContext.Equals(resourceContext)); - foreach (var resourceItem in Dictionary[pluginContext].Values) + if (resourceItem != null && resourceItem.Instance == null) { - resourceItem.Dispose(); + var instance = ComponentActivator.CreateInstance + ( + resourceItem.ResourceClass, + resourceContext, + _httpServerContext, + _componentHub, + resourceContext.ApplicationContext + ); + + if (resourceItem.Cache) + { + resourceItem.Instance = instance; + } + + return instance; } - Dictionary.Remove(pluginContext); + return resourceItem?.Instance as IResource; } /// @@ -371,26 +421,53 @@ private void OnRemoveResource(IResourceContext resourceContext) } /// - /// Information about the component is collected and prepared for output in the log. + /// Raises the event when an plugin is added. /// - /// The context of the plugin. - /// A list of log entries. - /// The shaft deep. - public void PrepareForLog(IPluginContext pluginContext, IList output, int deep) + /// The source of the event. + /// The context of the plugin being added. + private void OnAddPlugin(object sender, IPluginContext e) { - foreach (var resourcenItem in GetResorceItems(pluginContext)) - { - output.Add - ( - string.Empty.PadRight(deep) + - InternationalizationManager.I18N - ( - "webexpress:resourcemanager.resource", - resourcenItem.ResourceId, - string.Join(",", resourcenItem.ModuleId) - ) - ); - } + Register(e); + } + + /// + /// Raises the event when a plugin is removed. + /// + /// The source of the event. + /// The context of the plugin being removed. + private void OnRemovePlugin(object sender, IPluginContext e) + { + Remove(e); + } + /// + /// Raises the event when an application is removed. + /// + /// The source of the event. + /// The context of the application being removed. + private void OnRemoveApplication(object sender, IApplicationContext e) + { + Remove(e); + } + + /// + /// Raises the event when an application is added. + /// + /// The source of the event. + /// The context of the application being added. + private void OnAddApplication(object sender, IApplicationContext e) + { + Register(e); + } + + /// + /// Release of unmanaged resources reserved during use. + /// + public void Dispose() + { + _componentHub.PluginManager.AddPlugin -= OnAddPlugin; + _componentHub.PluginManager.RemovePlugin -= OnRemovePlugin; + _componentHub.ApplicationManager.AddApplication -= OnAddApplication; + _componentHub.ApplicationManager.RemoveApplication -= OnRemoveApplication; } } } diff --git a/src/WebExpress.WebCore/WebResource/ResourceRest.cs b/src/WebExpress.WebCore/WebResource/ResourceRest.cs deleted file mode 100644 index c0051dd..0000000 --- a/src/WebExpress.WebCore/WebResource/ResourceRest.cs +++ /dev/null @@ -1,84 +0,0 @@ -using System.Linq; -using System.Text.Json; -using WebExpress.WebCore.WebMessage; - -namespace WebExpress.WebCore.WebResource -{ - public abstract class ResourceRest : Resource - { - /// - /// Constructor - /// - public ResourceRest() - { - - } - - /// - /// Initialization - /// - /// The context. - public override void Initialization(IResourceContext context) - { - base.Initialization(context); - } - - /// - /// Processing of the resource that was called via the get request. - /// - /// The request. - /// An enumeration of which json serializer can be serialized. - public virtual object GetData(Request request) - { - return null; - } - - /// - /// Processing of the resource that was called via the delete request. - /// - /// The id to delete. - /// The request. - /// The result of the deletion. - public virtual bool DeleteData(string id, Request request) - { - return false; - } - - /// - /// Processing of the resource. - /// - /// The request. - /// The response. - public override Response Process(Request request) - { - var options = new JsonSerializerOptions - { - WriteIndented = true - }; - - var response = new ResponseOK(); - var content = string.Empty; - - switch (request.Method) - { - case RequestMethod.GET: - { - content = JsonSerializer.Serialize(GetData(request), options); - - break; - } - case RequestMethod.DELETE: - { - content = JsonSerializer.Serialize(DeleteData(request.Uri.PathSegments.Last()?.ToString(), request), options); - - break; - } - }; - - response.Header.ContentLength = content.Length; - response.Content = content; - - return response; - } - } -} diff --git a/src/WebExpress.WebCore/WebRestAPI/CrudMethod.cs b/src/WebExpress.WebCore/WebRestAPI/CrudMethod.cs new file mode 100644 index 0000000..33f66be --- /dev/null +++ b/src/WebExpress.WebCore/WebRestAPI/CrudMethod.cs @@ -0,0 +1,30 @@ +using WebExpress.WebCore.WebMessage; + +namespace WebExpress.WebCore.WebRestApi +{ + /// + /// Represents the crud methods for a rest api. + /// + public enum CrudMethod + { + /// + /// Represents the HTTP POST method. + /// + POST = RequestMethod.POST, + + /// + /// Represents the HTTP GET method. + /// + GET = RequestMethod.GET, + + /// + /// Represents the HTTP PATCH method. + /// + PATCH = RequestMethod.PATCH, + + /// + /// Represents the HTTP DELETE method. + /// + DELETE = RequestMethod.DELETE + } +} diff --git a/src/WebExpress.WebCore/WebRestAPI/IRestApi.cs b/src/WebExpress.WebCore/WebRestAPI/IRestApi.cs new file mode 100644 index 0000000..df36426 --- /dev/null +++ b/src/WebExpress.WebCore/WebRestAPI/IRestApi.cs @@ -0,0 +1,36 @@ +using WebExpress.WebCore.WebEndpoint; +using WebExpress.WebCore.WebMessage; + +namespace WebExpress.WebCore.WebRestApi +{ + /// + /// Defines the contract for a rest api resource. + /// + public interface IRestApi : IEndpoint + { + /// + /// Creates data. + /// + /// The request. + void CreateData(Request request); + + /// + /// Gets data. + /// + /// The request. + /// The data. + object GetData(Request request); + + /// + /// Updates data. + /// + /// The request. + void UpdateData(Request request); + + /// + /// Deletes data. + /// + /// The request. + void DeleteData(Request request); + } +} diff --git a/src/WebExpress.WebCore/WebRestAPI/IRestApiContext.cs b/src/WebExpress.WebCore/WebRestAPI/IRestApiContext.cs new file mode 100644 index 0000000..5960b70 --- /dev/null +++ b/src/WebExpress.WebCore/WebRestAPI/IRestApiContext.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; +using WebExpress.WebCore.WebEndpoint; + +namespace WebExpress.WebCore.WebRestApi +{ + /// + /// Defines the context for a rest api resource, providing access to various related contexts and properties. + /// + public interface IRestApiContext : IEndpointContext + { + /// + /// Returns the crud methods. + /// + IEnumerable Methods { get; } + + /// + /// Returns the version number of the rest api. + /// + uint Version { get; } + } +} diff --git a/src/WebExpress.WebCore/WebRestAPI/IRestApiManager.cs b/src/WebExpress.WebCore/WebRestAPI/IRestApiManager.cs new file mode 100644 index 0000000..79f079c --- /dev/null +++ b/src/WebExpress.WebCore/WebRestAPI/IRestApiManager.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections.Generic; +using WebExpress.WebCore.WebApplication; +using WebExpress.WebCore.WebComponent; +using WebExpress.WebCore.WebPlugin; + +namespace WebExpress.WebCore.WebRestApi +{ + /// + /// The page manager manages rest api resources, which can be called with a URI (Uniform Resource Identifier). + /// + public interface IRestApiManager : IComponentManager + { + /// + /// An event that fires when an rest api is added. + /// + event EventHandler AddRestApi; + + /// + /// An event that fires when an rest api is removed. + /// + event EventHandler RemoveRestApi; + + /// + /// Returns all rest api contexts. + /// + IEnumerable RestApis { get; } + + /// + /// Returns an enumeration of all containing rest api contexts of a plugin. + /// + /// A context of a plugin whose rest apis are to be registered. + /// An enumeration of rest api contexts. + IEnumerable GetRestApi(IPluginContext pluginContext); + + /// + /// Returns an enumeration of rest api contextes. + /// + /// The rest api type. + /// An enumeration of rest api contextes. + IEnumerable GetRestApi() where T : IRestApi; + + /// + /// Returns an enumeration of rest api contextes. + /// + /// The rest api type. + /// An enumeration of rest api contextes. + IEnumerable GetRestApi(Type pageType); + + /// + /// Returns an enumeration of rest api contextes. + /// + /// The rest api type. + /// The context of the application. + /// An enumeration of rest api contextes. + IEnumerable GetRestApi(Type restApiType, IApplicationContext applicationContext); + + /// + /// Returns an enumeration of rest api contextes. + /// + /// The rest api type. + /// The context of the application. + /// An enumeration of rest api contextes. + IEnumerable GetRestApi(IApplicationContext applicationContext) where T : IRestApi; + + /// + /// Returns the rest api context. + /// + /// The context of the application. + /// The rest api id. + /// An rest api context or null. + IRestApiContext GetRestApi(IApplicationContext applicationContext, string restApiId); + + /// + /// Returns the rest api context. + /// + /// The application id. + /// The rest api id. + /// An rest api context or null. + IRestApiContext GetRestApi(string applicationId, string restApiId); + } +} diff --git a/src/WebExpress.WebCore/WebRestAPI/Model/RestApiDictionary.cs b/src/WebExpress.WebCore/WebRestAPI/Model/RestApiDictionary.cs new file mode 100644 index 0000000..f1fac46 --- /dev/null +++ b/src/WebExpress.WebCore/WebRestAPI/Model/RestApiDictionary.cs @@ -0,0 +1,133 @@ +using System; +using System.Collections.Generic; +using WebExpress.WebCore.WebApplication; +using WebExpress.WebCore.WebPlugin; + +namespace WebExpress.WebCore.WebRestApi.Model +{ + /// + /// Represents a dictionary that maps plugin contexts to application contexts, rest api types, and rest api items. + /// key = plugin context + /// value = { key = rest api id, value = rest api item } + /// + internal class RestApiDictionary : Dictionary>> + { + /// + /// Adds a rest api item to the dictionary. + /// + /// The plugin context. + /// The application context. + /// The rest api item. + /// True if the rest api item was added successfully, false if an element with the same status code already exists. + public bool AddRestApiItem(IPluginContext pluginContext, IApplicationContext applicationContext, RestApiItem restApiItem) + { + var type = restApiItem.RestApiClass; + + if (!typeof(IRestApi).IsAssignableFrom(type)) + { + return false; + } + + if (!ContainsKey(pluginContext)) + { + this[pluginContext] = []; + } + + var appContextDict = this[pluginContext]; + + if (!appContextDict.ContainsKey(applicationContext)) + { + appContextDict[applicationContext] = []; + } + + var restApiDict = appContextDict[applicationContext]; + + if (!restApiDict.ContainsKey(type)) + { + restApiDict[type] = restApiItem; + return true; + } + + return false; // item with the same rest api class already exists + } + + /// + /// Removes a rest api from the dictionary. + /// + /// The plugin context. + /// The application context. + public void RemoveRestApi(IPluginContext pluginContext, IApplicationContext applicationContext) + where TRestApi : IRestApi + { + var type = typeof(TRestApi); + + if (ContainsKey(pluginContext)) + { + var appContextDict = this[pluginContext]; + + if (appContextDict.ContainsKey(applicationContext)) + { + var restApiDict = appContextDict[applicationContext]; + + if (restApiDict.ContainsKey(type)) + { + restApiDict.Remove(type); + + if (restApiDict.Count == 0) + { + appContextDict.Remove(applicationContext); + + if (appContextDict.Count == 0) + { + Remove(pluginContext); + } + } + } + } + } + } + + /// + /// Returns the rest api items from the dictionary. + /// + /// The type of rest api. + /// The application context. + /// An IEnumerable of rest api items + public IEnumerable GetRestApiItems(IApplicationContext applicationContext) + where TRestApi : IRestApi + { + return GetRestApiItems(applicationContext, typeof(TRestApi)); + } + + /// + /// Returns the rest api items from the dictionary. + /// + /// The application context. + /// The type of rest api. + /// An IEnumerable of rest api items + public IEnumerable GetRestApiItems(IApplicationContext applicationContext, Type restApiType) + { + if (!typeof(IRestApi).IsAssignableFrom(restApiType)) + { + return []; + } + + if (ContainsKey(applicationContext?.PluginContext)) + { + var appContextDict = this[applicationContext?.PluginContext]; + + if (appContextDict.ContainsKey(applicationContext)) + { + var restApiDict = appContextDict[applicationContext]; + + if (restApiDict.ContainsKey(restApiType)) + { + return [restApiDict[restApiType]]; + } + } + } + + return []; + } + } +} diff --git a/src/WebExpress.WebCore/WebRestAPI/Model/RestApiItem.cs b/src/WebExpress.WebCore/WebRestAPI/Model/RestApiItem.cs new file mode 100644 index 0000000..ff43210 --- /dev/null +++ b/src/WebExpress.WebCore/WebRestAPI/Model/RestApiItem.cs @@ -0,0 +1,111 @@ +using System; +using System.Collections.Generic; +using WebExpress.WebCore.WebApplication; +using WebExpress.WebCore.WebComponent; +using WebExpress.WebCore.WebCondition; +using WebExpress.WebCore.WebEndpoint; +using WebExpress.WebCore.WebPlugin; +using WebExpress.WebCore.WebUri; + +namespace WebExpress.WebCore.WebRestApi.Model +{ + /// + /// A rest api resource that contains meta information about a rest api resource. + /// + internal class RestApiItem : IDisposable + { + /// + /// Returns the endpoint id. + /// + public IComponentId EndpointId { get; internal set; } + + /// + /// Returns the associated plugin context. + /// + public IPluginContext PluginContext { get; internal set; } + + /// + /// Returns the corresponding application context. + /// + public IApplicationContext ApplicationContext { get; internal set; } + + /// + /// Returns or sets the type of rest api resource. + /// + public Type RestApiClass { get; internal set; } + + /// + /// Returns or sets the instance of the rest api resource, if the rest api resource is cached, otherwise null. + /// + public IRestApi Instance { get; internal set; } + + /// + /// Returns or sets the paths of the resource. + /// + public UriEndpoint ContextPath { get; internal set; } + + /// + /// Returns or sets the path segment. + /// + public IUriPathSegment PathSegment { get; internal set; } + + /// + /// Returns or sets whether all subpaths should be taken into sitemap. + /// + public bool IncludeSubPaths { get; internal set; } + + /// + /// Returns the conditions that must be met for the rest api resource to be active. + /// + public IEnumerable Conditions { get; internal set; } + + /// + /// Returns the crud methods. + /// + public IEnumerable Methods { get; internal set; } + + /// + /// Returns the version number of the rest api. + /// + public uint Version { get; internal set; } + + /// + /// Returns whether the resource is created once and reused each time it is called. + /// + public bool Cache { get; internal set; } + + /// + /// Returns the attributes associated with the page. + /// + public IEnumerable Attributes { get; internal set; } + + /// + /// Returns the rest api contexts. + /// + public IRestApiContext RestApiContext { get; internal set; } + + /// + /// Initializes a new instance of the class. + /// + /// The endpoint manager responsible for managing endpoints. + internal RestApiItem(IEndpointManager endpointManager) + { + } + + /// + /// Performs application-specific tasks related to sharing, returning, or resetting unmanaged rest api resources. + /// + public void Dispose() + { + } + + /// + /// Convert the resource element to a string. + /// + /// The rest api resource element in its string representation. + public override string ToString() + { + return $"RestApi: '{RestApiContext?.EndpointId}'"; + } + } +} diff --git a/src/WebExpress.WebCore/WebRestAPI/RestApi.cs b/src/WebExpress.WebCore/WebRestAPI/RestApi.cs new file mode 100644 index 0000000..eec24c4 --- /dev/null +++ b/src/WebExpress.WebCore/WebRestAPI/RestApi.cs @@ -0,0 +1,68 @@ +using WebExpress.WebCore.WebMessage; + +namespace WebExpress.WebCore.WebRestApi +{ + /// + /// The prototype of a rest api. + /// + public abstract class RestApi : IRestApi + { + /// + /// Returns the rest api context. + /// + public IRestApiContext RestApiContext { get; private set; } + + /// + /// Initializes a new instance of the class. + /// + /// The context of the rest api resource. + public RestApi(IRestApiContext restApiContext) + { + RestApiContext = restApiContext; + } + + /// + /// Creates data. + /// + /// The request. + public virtual void CreateData(Request request) + { + + } + + /// + /// Gets data. + /// + /// The data. + public virtual object GetData(Request request) + { + return null; + } + + /// + /// Updates data. + /// + /// The request. + public virtual void UpdateData(Request request) + { + + } + + /// + /// Deletes data. + /// + /// The request. + public virtual void DeleteData(Request request) + { + + } + + /// + /// Performs application-specific tasks related to sharing, returning, or resetting unmanaged resources. + /// + public virtual void Dispose() + { + + } + } +} diff --git a/src/WebExpress.WebCore/WebRestAPI/RestApiContext.cs b/src/WebExpress.WebCore/WebRestAPI/RestApiContext.cs new file mode 100644 index 0000000..3dc09ce --- /dev/null +++ b/src/WebExpress.WebCore/WebRestAPI/RestApiContext.cs @@ -0,0 +1,88 @@ +using System; +using System.Collections.Generic; +using WebExpress.WebCore.WebApplication; +using WebExpress.WebCore.WebComponent; +using WebExpress.WebCore.WebCondition; +using WebExpress.WebCore.WebEndpoint; +using WebExpress.WebCore.WebPlugin; +using WebExpress.WebCore.WebUri; + +namespace WebExpress.WebCore.WebRestApi +{ + /// + /// Represents the context of a rest api resource. + /// + public class RestApiContext : IRestApiContext + { + /// + /// Returns the associated plugin context. + /// + public IPluginContext PluginContext { get; internal set; } + + /// + /// Returns the corresponding application context. + /// + public IApplicationContext ApplicationContext { get; internal set; } + + /// + /// Returns the conditions that must be met for the resource to be active. + /// + public IEnumerable Conditions { get; internal set; } = []; + + /// + /// Returns the crud methods. + /// + public IEnumerable Methods { get; internal set; } = []; + + /// + /// Returns the endpoint id. + /// + public IComponentId EndpointId { get; internal set; } + + /// + /// Returns the version number of the rest api. + /// + public uint Version { get; internal set; } + + /// + /// Returns whether the resource is created once and reused each time it is called. + /// + public bool Cache { get; internal set; } + + /// + /// Returns or sets whether all subpaths should be taken into sitemap. + /// + public bool IncludeSubPaths { get; internal set; } + + /// + /// Returns the attributes associated with the page. + /// + public IEnumerable Attributes { get; internal set; } + + /// + /// Returns the context path. + /// + public UriEndpoint ContextPath { get; internal set; } + + /// + /// Returns the internal routing path for the endpoint. + /// + public IRoute Route { get; internal set; } + + /// + /// Initializes a new instance of the class with the specified parent type and context path. + /// + public RestApiContext() + { + } + + /// + /// Returns a string that represents the current object. + /// + /// A string that represents the current object. + public override string ToString() + { + return $"RestApi: {EndpointId}"; + } + } +} diff --git a/src/WebExpress.WebCore/WebRestAPI/RestApiManager.cs b/src/WebExpress.WebCore/WebRestAPI/RestApiManager.cs new file mode 100644 index 0000000..506af85 --- /dev/null +++ b/src/WebExpress.WebCore/WebRestAPI/RestApiManager.cs @@ -0,0 +1,550 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Text; +using System.Text.Json; +using System.Text.RegularExpressions; +using WebExpress.WebCore.Internationalization; +using WebExpress.WebCore.WebApplication; +using WebExpress.WebCore.WebAttribute; +using WebExpress.WebCore.WebComponent; +using WebExpress.WebCore.WebCondition; +using WebExpress.WebCore.WebEndpoint; +using WebExpress.WebCore.WebMessage; +using WebExpress.WebCore.WebPlugin; +using WebExpress.WebCore.WebRestApi.Model; +using WebExpress.WebCore.WebUri; + +namespace WebExpress.WebCore.WebRestApi +{ + /// + /// The rest api manager manages rest api resources, which can be called with a URI (Uniform page Identifier). + /// + public partial class RestApiManager : IRestApiManager + { + private readonly IComponentHub _componentHub; + private readonly IHttpServerContext _httpServerContext; + private readonly RestApiDictionary _dictionary = []; + private readonly JsonSerializerOptions _jsonOptions = new() { WriteIndented = true }; + + [GeneratedRegex(@"\.(?:_|V|v)(\d+)\.")] + private static partial Regex ApiVersionRegex(); + + /// + /// An event that fires when an rest api resource is added. + /// + public event EventHandler AddRestApi; + + /// + /// An event that fires when an rest api resource is removed. + /// + public event EventHandler RemoveRestApi; + + /// + /// Returns all rest api resource contexts. + /// + public IEnumerable RestApis => _dictionary.Values + .SelectMany(x => x.Values) + .SelectMany(x => x.Values) + .Select(x => x.RestApiContext); + + /// + /// Initializes a new instance of the class. + /// + /// The component hub. + /// The reference to the context of the host. + [SuppressMessage("CodeQuality", "IDE0051:Remove unused private members", Justification = "Used via Reflection.")] + private RestApiManager(IComponentHub componentHub, IHttpServerContext httpServerContext) + { + _componentHub = componentHub; + + _componentHub.PluginManager.AddPlugin += OnAddPlugin; + _componentHub.PluginManager.RemovePlugin += OnRemovePlugin; + _componentHub.ApplicationManager.AddApplication += OnAddApplication; + _componentHub.ApplicationManager.RemoveApplication += OnRemoveApplication; + + var endpointtRegistration = new EndpointRegistration() + { + EndpointResolver = (type, applicationContext) => applicationContext != null ? GetRestApi(type, applicationContext) : GetRestApi(type), + EndpointsResolver = () => RestApis, + HandleRequest = (request, endpointContext) => + { + var restApiContext = endpointContext as IRestApiContext; + var restApi = CreateApiInstance(restApiContext) as IRestApi; + + if (restApiContext.Methods.Any(x => x.Equals((CrudMethod)request.Method))) + { + switch (request.Method) + { + case RequestMethod.POST: + restApi.CreateData(request); + + return new ResponseOK(); + case RequestMethod.GET: + var data = restApi.GetData(request); + if (data != null) + { + var jsonData = JsonSerializer.Serialize(data, _jsonOptions); + var content = Encoding.UTF8.GetBytes(jsonData); + + return new ResponseOK + { + Content = content + }; + } + + return new ResponseOK(); + case RequestMethod.PATCH: + restApi.UpdateData(request); + + return new ResponseOK(); + case RequestMethod.DELETE: + restApi.DeleteData(request); + + return new ResponseOK(); + } + } + + return new ResponseBadRequest() + { + Content = I18N.Translate("webexpress.webcore:restapimanager.methodnotsupported", request.Method.ToString()) + }; + } + }; + + AddRestApi += (sender, e) => endpointtRegistration.AddEndpoint?.Invoke(sender, e); + RemoveRestApi += (sender, e) => endpointtRegistration.RemoveEndpoint?.Invoke(sender, e); + + _componentHub.EndpointManager.Register(endpointtRegistration); + + _httpServerContext = httpServerContext; + + _httpServerContext.Log.Debug + ( + I18N.Translate("webexpress.webcore:restapimanager.initialization") + ); + } + + /// + /// Returns an enumeration of all containing page contexts of a plugin. + /// + /// A context of a plugin whose pages are to be registered. + /// An enumeration of rest api resource contexts. + public IEnumerable GetRestApi(IPluginContext pluginContext) + { + if (_dictionary.TryGetValue(pluginContext, out var pluginResources)) + { + return pluginResources + .SelectMany(x => x.Value) + .Select(x => x.Value.RestApiContext); + } + + return []; + } + + /// + /// Returns an enumeration of rest api resource contextes. + /// + /// The rest api resource type. + /// An enumeration of rest api resource contextes. + public IEnumerable GetRestApi() where T : IRestApi + { + return GetRestApi(typeof(T)); + } + + /// + /// Returns an enumeration of rest api resource contextes. + /// + /// The rest api resource type. + /// An enumeration of rest api resource contextes. + public IEnumerable GetRestApi(Type restApiType) + { + return _dictionary.Values + .SelectMany(x => x.Values) + .SelectMany(x => x.Values) + .Where(x => x.RestApiClass.Equals(restApiType)) + .Select(x => x.RestApiContext); + } + + /// + /// Returns an enumeration of rest api resource contextes. + /// + /// The page type. + /// The context of the application. + /// An enumeration of page contextes. + public IEnumerable GetRestApi(Type restApiType, IApplicationContext applicationContext) + { + return _dictionary.Values + .SelectMany(x => x.Values) + .SelectMany(x => x.Values) + .Where(x => x.RestApiClass.Equals(restApiType)) + .Where(x => x.RestApiContext.ApplicationContext.Equals(applicationContext)) + .Select(x => x.RestApiContext); + } + + /// + /// Returns an enumeration of rest api resource contextes. + /// + /// The rest api resource type. + /// The context of the application. + /// An enumeration of rest api resource contextes. + public IEnumerable GetRestApi(IApplicationContext applicationContext) where T : IRestApi + { + return _dictionary.Values + .SelectMany(x => x.Values) + .SelectMany(x => x.Values) + .Where(x => x.RestApiClass.Equals(typeof(T))) + .Where(x => x.RestApiContext.ApplicationContext.Equals(applicationContext)) + .Select(x => x.RestApiContext); + } + + /// + /// Returns the rest api resource context. + /// + /// The context of the application. + /// The rest api resource id. + /// An rest api resource context or null. + public IRestApiContext GetRestApi(IApplicationContext applicationContext, string restApiId) + { + return _dictionary.Values + .SelectMany(x => x.Values) + .SelectMany(x => x.Values) + .Where(x => x.RestApiContext.ApplicationContext.Equals(applicationContext)) + .Where(x => x.RestApiContext.EndpointId.Equals(restApiId)) + .Select(x => x.RestApiContext) + .FirstOrDefault(); + } + + /// + /// Returns the rest api resource context. + /// + /// The application id. + /// The rest api resource id. + /// An rest api resource context or null. + public IRestApiContext GetRestApi(string applicationId, string restApiId) + { + return _dictionary.Values + .SelectMany(x => x.Values) + .SelectMany(x => x.Values) + .Where(x => x.RestApiContext.ApplicationContext.ApplicationId.Equals(applicationId)) + .Where(x => x.RestApiContext.EndpointId.Equals(restApiId)) + .Select(x => x.RestApiContext) + .FirstOrDefault(); + } + + /// + /// Creates a new rest api resource and returns it. If a rest api resource already exists (through caching), the existing instance is returned. + /// + /// The context used for rest api resource creation. + /// The created or cached rest api resource. + private IRestApi CreateApiInstance(IRestApiContext apiContext) + { + var resourceItem = _dictionary.Values + .SelectMany(x => x.Values) + .SelectMany(x => x.Values) + .FirstOrDefault(x => x.RestApiContext.Equals(apiContext)); + + if (resourceItem != null && resourceItem.Instance == null) + { + var instance = ComponentActivator.CreateInstance + ( + resourceItem.RestApiClass, + apiContext, + _httpServerContext, + _componentHub, + apiContext.ApplicationContext + ); + + if (resourceItem.Cache) + { + resourceItem.Instance = instance; + } + + return instance; + } + + return resourceItem?.Instance as IRestApi; + } + + /// + /// Discovers and binds rest apis to an application. + /// + /// The context of the plugin whose rest apis are to be associated. + private void Register(IPluginContext pluginContext) + { + if (_dictionary.ContainsKey(pluginContext)) + { + return; + } + + Register(pluginContext, _componentHub.ApplicationManager.GetApplications(pluginContext)); + } + + /// + /// Discovers and binds rest apis to an application. + /// + /// The context of the application whose rest apis are to be associated. + private void Register(IApplicationContext applicationContext) + { + foreach (var pluginContext in _componentHub.PluginManager.GetPlugins(applicationContext)) + { + if (_dictionary.TryGetValue(pluginContext, out var appDict) && appDict.ContainsKey(applicationContext)) + { + continue; + } + + Register(pluginContext, [applicationContext]); + } + } + + /// + /// Registers rest apis for a given plugin and application context. + /// + /// The plugin context. + /// The application context (optional). + private void Register(IPluginContext pluginContext, IEnumerable applicationContexts) + { + var assembly = pluginContext?.Assembly; + + foreach (var restApiType in assembly.GetTypes() + .Where(x => x.IsClass == true && x.IsSealed && x.IsPublic) + .Where(x => x.GetInterface(typeof(IRestApi).Name) != null)) + { + var id = restApiType.FullName?.ToLower(); + var segment = default(ISegmentAttribute); + var title = restApiType.Name; + var contextPath = string.Empty; + var includeSubPaths = false; + var conditions = new List(); + var cache = false; + var methods = new List(); + var match = ApiVersionRegex().Match(id); + var versionSegment = match.Success ? match.Groups[0].Value.Replace(".", "") : ""; + var version = match.Success && uint.TryParse(match.Groups[1].Value, out var result) ? result : 1u; + var attributes = restApiType.CustomAttributes + .Where(x => !x.AttributeType.GetInterfaces().Contains(typeof(IEndpointAttribute)) && + !x.AttributeType.GetInterfaces().Contains(typeof(IPageAttribute))); + + foreach (var customAttribute in restApiType.CustomAttributes + .Where(x => x.AttributeType.GetInterfaces().Contains(typeof(IEndpointAttribute)))) + { + if (customAttribute.AttributeType.GetInterfaces().Contains(typeof(ISegmentAttribute))) + { + segment = restApiType.GetCustomAttributes(customAttribute.AttributeType, false).FirstOrDefault() as ISegmentAttribute; + } + else if (customAttribute.AttributeType == typeof(ContextPathAttribute)) + { + contextPath = customAttribute.ConstructorArguments.FirstOrDefault().Value?.ToString(); + } + else if (customAttribute.AttributeType == typeof(IncludeSubPathsAttribute)) + { + includeSubPaths = Convert.ToBoolean(customAttribute.ConstructorArguments.FirstOrDefault().Value); + } + else if (customAttribute.AttributeType.Name == typeof(ConditionAttribute<>).Name + && customAttribute.AttributeType.Namespace == typeof(ConditionAttribute<>).Namespace) + { + var condition = customAttribute.AttributeType.GenericTypeArguments.FirstOrDefault(); + conditions.Add(Activator.CreateInstance(condition) as ICondition); + } + else if (customAttribute.AttributeType.Name == typeof(MethodAttribute).Name + && customAttribute.AttributeType.Namespace == typeof(MethodAttribute).Namespace) + { + var method = (CrudMethod)customAttribute.ConstructorArguments.FirstOrDefault().Value; + methods.Add(method); + } + else if (customAttribute.AttributeType == typeof(CacheAttribute)) + { + cache = true; + } + } + + // assign the rest api to existing applications + foreach (var applicationContext in applicationContexts) + { + var prefix = applicationContext.ContextPath.Concat + ( + applicationContext.PluginContext != pluginContext + ? pluginContext.PluginName.ToLower() + : "" + ); + + var routePath = EndpointManager.CreateEndpointRoute + ( + restApiType, + prefix, + segment, + [new UriPathSegmentConstant("api"), new UriPathSegmentVariableInt($"{version}") { VariableName = "_apiVersion" }], + ["api", "restapi", "rest"] + ).RemoveSegment(versionSegment); + + var restApiContext = new RestApiContext() + { + EndpointId = new ComponentId(id), + PluginContext = pluginContext, + ApplicationContext = applicationContext, + Route = routePath, + Cache = cache, + Conditions = conditions, + IncludeSubPaths = includeSubPaths, + Attributes = attributes.Select(x => x.AttributeType), + Version = version, + Methods = methods.Distinct() + }; + + var restApiItem = new RestApiItem(_componentHub.EndpointManager) + { + EndpointId = new ComponentId(restApiType.FullName), + PluginContext = pluginContext, + ApplicationContext = applicationContext, + RestApiContext = restApiContext, + RestApiClass = restApiType, + Methods = methods.Distinct(), + Version = version, + Cache = cache, + Conditions = conditions, + IncludeSubPaths = includeSubPaths, + Attributes = attributes.Select(x => x.AttributeType) + }; + + if (_dictionary.AddRestApiItem(pluginContext, applicationContext, restApiItem)) + { + OnAddRestApi(restApiItem.RestApiContext); + + _httpServerContext?.Log.Debug + ( + I18N.Translate + ( + "webexpress.webcore:restapimanager.addrestapi", + id, + applicationContext.ApplicationId + ) + ); + } + } + } + } + + /// + /// Removes all pages associated with the specified plugin context. + /// + /// The context of the plugin that contains the rest api resources to remove. + public void Remove(IPluginContext pluginContext) + { + if (pluginContext == null) + { + return; + } + + // the plugin has not been registered in the manager + if (_dictionary.TryGetValue(pluginContext, out var value)) + { + foreach (var resourceItem in value.Values + .SelectMany(x => x.Values)) + { + OnRemoveRestApi(resourceItem.RestApiContext); + resourceItem.Dispose(); + } + + _dictionary.Remove(pluginContext); + } + } + + /// + /// Removes all events associated with the specified application context. + /// + /// The context of the application that contains the events to remove. + internal void Remove(IApplicationContext applicationContext) + { + if (applicationContext == null) + { + return; + } + + foreach (var pluginDict in _dictionary.Values) + { + foreach (var appDict in pluginDict.Where(x => x.Key == applicationContext).Select(x => x.Value)) + { + foreach (var resourceItem in appDict.Values) + { + OnRemoveRestApi(resourceItem.RestApiContext); + resourceItem.Dispose(); + } + } + + pluginDict.Remove(applicationContext); + } + } + + /// + /// Raises the AddRestApi event. + /// + /// The rest api resource context. + private void OnAddRestApi(IRestApiContext resourceContext) + { + AddRestApi?.Invoke(this, resourceContext); + } + + /// + /// Raises the RemoveRestApi event. + /// + /// The rest api resource context. + private void OnRemoveRestApi(IRestApiContext pageContext) + { + RemoveRestApi?.Invoke(this, pageContext); + } + + /// + /// Raises the event when an plugin is added. + /// + /// The source of the event. + /// The context of the plugin being added. + private void OnAddPlugin(object sender, IPluginContext e) + { + Register(e); + } + + /// + /// Raises the event when a plugin is removed. + /// + /// The source of the event. + /// The context of the plugin being removed. + private void OnRemovePlugin(object sender, IPluginContext e) + { + Remove(e); + } + + /// + /// Raises the event when an application is removed. + /// + /// The source of the event. + /// The context of the application being removed. + private void OnRemoveApplication(object sender, IApplicationContext e) + { + Remove(e); + } + + /// + /// Raises the event when an application is added. + /// + /// The source of the event. + /// The context of the application being added. + private void OnAddApplication(object sender, IApplicationContext e) + { + Register(e); + } + + /// + /// Release of unmanaged resources reserved during use. + /// + public void Dispose() + { + _componentHub.PluginManager.AddPlugin -= OnAddPlugin; + _componentHub.PluginManager.RemovePlugin -= OnRemovePlugin; + _componentHub.ApplicationManager.AddApplication -= OnAddApplication; + _componentHub.ApplicationManager.RemoveApplication -= OnRemoveApplication; + + GC.SuppressFinalize(this); + } + } +} diff --git a/src/WebExpress.WebCore/WebSection/ISection.cs b/src/WebExpress.WebCore/WebSection/ISection.cs new file mode 100644 index 0000000..98cd4da --- /dev/null +++ b/src/WebExpress.WebCore/WebSection/ISection.cs @@ -0,0 +1,9 @@ +namespace WebExpress.WebCore.WebSection +{ + /// + /// Interface of a section. + /// + public interface ISection + { + } +} diff --git a/src/WebExpress.WebCore/WebSession/AuthorizationService.cs b/src/WebExpress.WebCore/WebSession/AuthorizationService.cs index 8654c7b..5818224 100644 --- a/src/WebExpress.WebCore/WebSession/AuthorizationService.cs +++ b/src/WebExpress.WebCore/WebSession/AuthorizationService.cs @@ -1,12 +1,18 @@ -namespace WebExpress.WebCore.WebSession +using WebExpress.WebCore.WebSession.Model; + +namespace WebExpress.WebCore.WebSession { + + /// + /// Represents an abstract authorization service. + /// public abstract class AuthorizationService { /// - /// Prüft ob der authentifizierte Nutzer autorisiert ist + /// Checks if the authenticated user is authorized. /// - /// Die aktuelle Session - /// true, wenn autorisiert false sonst + /// The current session. + /// true if authorized, false otherwise. public abstract bool Authorization(Session session); } } diff --git a/src/WebExpress.WebCore/WebSession/ISessionManager.cs b/src/WebExpress.WebCore/WebSession/ISessionManager.cs new file mode 100644 index 0000000..7f81cbf --- /dev/null +++ b/src/WebExpress.WebCore/WebSession/ISessionManager.cs @@ -0,0 +1,19 @@ +using WebExpress.WebCore.WebComponent; +using WebExpress.WebCore.WebMessage; +using WebExpress.WebCore.WebSession.Model; + +namespace WebExpress.WebCore.WebSession +{ + /// + /// Represents a session manager that handles session creation and retrieval. + /// + public interface ISessionManager : IComponentManager + { + /// + /// Creates a session or returns an existing session based on the provided request. + /// + /// The request. + /// The session. + Session GetSession(Request request); + } +} diff --git a/src/WebExpress.WebCore/WebSession/Session.cs b/src/WebExpress.WebCore/WebSession/Model/Session.cs similarity index 61% rename from src/WebExpress.WebCore/WebSession/Session.cs rename to src/WebExpress.WebCore/WebSession/Model/Session.cs index 8cfd1bd..709ee9b 100644 --- a/src/WebExpress.WebCore/WebSession/Session.cs +++ b/src/WebExpress.WebCore/WebSession/Model/Session.cs @@ -1,7 +1,9 @@ using System; using System.Collections.Generic; +using System.Linq; +using System.Reflection; -namespace WebExpress.WebCore.WebSession +namespace WebExpress.WebCore.WebSession.Model { /// /// Represents a session.Through a session, session data can be assigned to @@ -31,7 +33,7 @@ public class Session public Dictionary Properties { get; private set; } /// - /// Constructor + /// Initializes a new instance of the class. /// public Session() : this(Guid.NewGuid()) @@ -39,7 +41,7 @@ public Session() } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The session id. public Session(Guid id) @@ -56,7 +58,7 @@ public Session(Guid id) /// /// The type of the property. /// The property or null. - public T GetProperty() where T : class, ISessionProperty, new() + public T GetProperty() where T : class, ISessionProperty { lock (Properties) { @@ -73,17 +75,47 @@ public Session(Guid id) /// Returns a property if it already exists. Otherwise, a new property will be created. /// /// The type of the property. - /// The property or null. - public T GetOrCreateProperty() where T : class, ISessionProperty, new() + /// The parameters to pass to the constructor of the property if it needs to be created. + /// The property or null if it cannot be created. + public T GetOrCreateProperty(params object[] parameters) where T : class, ISessionProperty { + var type = typeof(T); lock (Properties) { if (Properties.ContainsKey(typeof(T))) { - return Properties[typeof(T)] as T; + return Properties[type] as T; } - var property = new T(); + var flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; + var constructors = type.GetConstructors(flags); + + if (constructors != null || parameters.Length > 0) + { + foreach (var constructor in constructors.OrderByDescending(x => x.GetParameters().Length)) + { + // injection + var constructorParameters = constructor.GetParameters(); + var parameterValues = constructorParameters.Select + ( + x => parameters.Where + ( + y => y.GetType() == x.ParameterType || + x.ParameterType.IsAssignableFrom(y.GetType()) || + y.GetType().IsSubclassOf(x.ParameterType) + ).FirstOrDefault() ?? null + ).ToArray(); + + if (constructor.Invoke(parameterValues) is T injectionProperty) + { + SetProperty(injectionProperty); + + return injectionProperty; + } + } + } + + var property = Activator.CreateInstance(); SetProperty(property); return property; @@ -111,14 +143,11 @@ public void SetProperty(ISessionProperty property) /// Removes a property. /// /// The type of the property. - public void RemoveProperty() where T : class, ISessionProperty, new() + public void RemoveProperty() where T : class, ISessionProperty { lock (Properties) { - if (Properties.ContainsKey(typeof(T))) - { - Properties.Remove(typeof(T)); - } + Properties.Remove(typeof(T)); } } diff --git a/src/WebExpress.WebCore/WebSession/SessionDictionary.cs b/src/WebExpress.WebCore/WebSession/Model/SessionDictionary.cs similarity index 85% rename from src/WebExpress.WebCore/WebSession/SessionDictionary.cs rename to src/WebExpress.WebCore/WebSession/Model/SessionDictionary.cs index 9a27725..22b9a67 100644 --- a/src/WebExpress.WebCore/WebSession/SessionDictionary.cs +++ b/src/WebExpress.WebCore/WebSession/Model/SessionDictionary.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; -namespace WebExpress.WebCore.WebSession +namespace WebExpress.WebCore.WebSession.Model { /// /// Internal directory for storing session data. diff --git a/src/WebExpress.WebCore/WebSession/SessionProperty.cs b/src/WebExpress.WebCore/WebSession/Model/SessionProperty.cs similarity index 78% rename from src/WebExpress.WebCore/WebSession/SessionProperty.cs rename to src/WebExpress.WebCore/WebSession/Model/SessionProperty.cs index fc29716..59d2d63 100644 --- a/src/WebExpress.WebCore/WebSession/SessionProperty.cs +++ b/src/WebExpress.WebCore/WebSession/Model/SessionProperty.cs @@ -1,4 +1,4 @@ -namespace WebExpress.WebCore.WebSession +namespace WebExpress.WebCore.WebSession.Model { /// /// Base class of a property that can be assigned to a session. diff --git a/src/WebExpress.WebCore/WebSession/Model/SessionPropertyAuthentification.cs b/src/WebExpress.WebCore/WebSession/Model/SessionPropertyAuthentification.cs new file mode 100644 index 0000000..2220435 --- /dev/null +++ b/src/WebExpress.WebCore/WebSession/Model/SessionPropertyAuthentification.cs @@ -0,0 +1,27 @@ + +using WebExpress.WebCore.WebIdentity; + +namespace WebExpress.WebCore.WebSession.Model +{ + /// + /// Represents the authentication session property. + /// Authentication is the process of verifying the identity of a person or system to ensure that someone is who they claim to be. + /// + public class SessionPropertyAuthentification : SessionProperty + { + /// + /// Returns the identity. + /// + public IIdentity Identity { get; } + + /// + /// Initializes a new instance of the class. + /// + /// The identity to be set. + public SessionPropertyAuthentification(IIdentity identity) + { + Identity = identity; + } + + } +} diff --git a/src/WebExpress.WebCore/WebSession/Model/SessionPropertyAuthorization.cs b/src/WebExpress.WebCore/WebSession/Model/SessionPropertyAuthorization.cs new file mode 100644 index 0000000..d5f9384 --- /dev/null +++ b/src/WebExpress.WebCore/WebSession/Model/SessionPropertyAuthorization.cs @@ -0,0 +1,10 @@ +namespace WebExpress.WebCore.WebSession.Model +{ + /// + /// Represents a session property authorization. + /// Authorization is the process of determining which resources and actions are allowed for an authenticated person or system. + /// + public class SessionPropertyAuthorization : SessionProperty + { + } +} diff --git a/src/WebExpress.WebCore/WebSession/Model/SessionPropertyParameter.cs b/src/WebExpress.WebCore/WebSession/Model/SessionPropertyParameter.cs new file mode 100644 index 0000000..e1a01d1 --- /dev/null +++ b/src/WebExpress.WebCore/WebSession/Model/SessionPropertyParameter.cs @@ -0,0 +1,35 @@ +using System.Collections.Generic; +using WebExpress.WebCore.WebMessage; + +namespace WebExpress.WebCore.WebSession.Model +{ + /// + /// Represents a session property with parameters. + /// + public class SessionPropertyParameter : SessionProperty + { + /// + /// Returns the parameters. + /// + public Dictionary Params { get; } = []; + + /// + /// Initializes a new instance of the class. + /// + public SessionPropertyParameter() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The parameters. + public SessionPropertyParameter(params Parameter[] parameters) + { + foreach (var param in parameters) + { + Params.Add(param.Key, param); + } + } + } +} diff --git a/src/WebExpress.WebCore/WebSession/SessionManager.cs b/src/WebExpress.WebCore/WebSession/SessionManager.cs index 587a9d8..b2108f2 100644 --- a/src/WebExpress.WebCore/WebSession/SessionManager.cs +++ b/src/WebExpress.WebCore/WebSession/SessionManager.cs @@ -1,48 +1,38 @@ using System; -using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using WebExpress.WebCore.Internationalization; using WebExpress.WebCore.WebComponent; using WebExpress.WebCore.WebMessage; -using WebExpress.WebCore.WebPlugin; +using WebExpress.WebCore.WebSession.Model; namespace WebExpress.WebCore.WebSession { - public class SessionManager : IComponent, ISystemComponent + /// + /// Represents a session manager that handles session creation and retrieval. + /// + public class SessionManager : ISessionManager, ISystemComponent { - /// - /// Returns or sets the reference to the context of the host. - /// - public IHttpServerContext HttpServerContext { get; private set; } - - /// - /// Returns the directory in which the sessions are stored on the server side. - /// - private SessionDictionary Dictionary { get; } = new SessionDictionary(); - - /// - /// Constructor - /// - internal SessionManager() - { - } + private readonly IHttpServerContext _httpServerContext; + private readonly SessionDictionary _dictionary = []; /// - /// Initialization + /// Initializes a new instance of the class. /// /// The reference to the context of the host. - public void Initialization(IHttpServerContext context) + [SuppressMessage("CodeQuality", "IDE0051:Remove unused private members", Justification = "Used via Reflection.")] + private SessionManager(IHttpServerContext context) { - HttpServerContext = context; + _httpServerContext = context; - HttpServerContext.Log.Debug + _httpServerContext.Log.Debug ( - InternationalizationManager.I18N("webexpress:sessionmanager.initialization") + I18N.Translate("webexpress.webcore:sessionmanager.initialization") ); } /// - /// Creates a session or returns an existing session. + /// Creates a session or returns an existing session based on the provided request. /// /// The request. /// The session. @@ -55,30 +45,30 @@ public Session GetSession(Request request) .Cookies?.Where(x => x.Name.Equals("session", StringComparison.OrdinalIgnoreCase)) .FirstOrDefault(); - Guid Guid = Guid.NewGuid(); + var guid = Guid.NewGuid(); try { - Guid = Guid.Parse(sessionCookie?.Value); + guid = Guid.Parse(sessionCookie?.Value); } catch { } - if (sessionCookie != null && Dictionary.ContainsKey(Guid)) + if (sessionCookie != null && _dictionary.TryGetValue(guid, out Session value)) { - session = Dictionary[Guid]; + session = value; session.Updated = DateTime.Now; } else { // no or invalid session => assign new session - session = new Session(Guid); + session = new Session(guid); - lock (Dictionary) + lock (_dictionary) { - Dictionary[Guid] = session; + _dictionary[guid] = session; } } @@ -86,13 +76,11 @@ public Session GetSession(Request request) } /// - /// Information about the component is collected and prepared for output in the log. + /// Release of unmanaged resources reserved during use. /// - /// The context of the plugin. - /// A list of log entries. - /// The shaft deep. - public void PrepareForLog(IPluginContext pluginContext, IList output, int deep) + public void Dispose() { + GC.SuppressFinalize(this); } } } diff --git a/src/WebExpress.WebCore/WebSession/SessionPropertyAuthentification.cs b/src/WebExpress.WebCore/WebSession/SessionPropertyAuthentification.cs deleted file mode 100644 index 204ff83..0000000 --- a/src/WebExpress.WebCore/WebSession/SessionPropertyAuthentification.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace WebExpress.WebCore.WebSession -{ - public class SessionPropertyAuthentification : SessionProperty - { - /// - /// Returns or sets the login name. - /// - public string Identification { get; set; } - - /// - /// Provides or sets the password. - /// - public string Password { get; set; } - } -} diff --git a/src/WebExpress.WebCore/WebSession/SessionPropertyAuthorization.cs b/src/WebExpress.WebCore/WebSession/SessionPropertyAuthorization.cs deleted file mode 100644 index 7d604b0..0000000 --- a/src/WebExpress.WebCore/WebSession/SessionPropertyAuthorization.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace WebExpress.WebCore.WebSession -{ - public class SessionPropertyAuthorization : SessionProperty - { - } -} diff --git a/src/WebExpress.WebCore/WebSession/SessionPropertyParameter.cs b/src/WebExpress.WebCore/WebSession/SessionPropertyParameter.cs deleted file mode 100644 index dd8d0de..0000000 --- a/src/WebExpress.WebCore/WebSession/SessionPropertyParameter.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System.Collections.Generic; -using WebExpress.WebCore.WebMessage; - -namespace WebExpress.WebCore.WebSession -{ - public class SessionPropertyParameter : SessionProperty - { - /// - /// Returns the parameters. - /// - public Dictionary Params { get; private set; } - - /// - /// Constructor - /// - public SessionPropertyParameter() - { - Params = new Dictionary(); - } - - /// - /// Constructor - /// - /// The parameters - public SessionPropertyParameter(Dictionary param) - { - Params = param; - } - } -} diff --git a/src/WebExpress.WebCore/WebSettingPage/ISettingCategory.cs b/src/WebExpress.WebCore/WebSettingPage/ISettingCategory.cs new file mode 100644 index 0000000..851ca0c --- /dev/null +++ b/src/WebExpress.WebCore/WebSettingPage/ISettingCategory.cs @@ -0,0 +1,9 @@ +namespace WebExpress.WebCore.WebSettingPage +{ + /// + /// Represents a category of settings in the web application. + /// + public interface ISettingCategory + { + } +} diff --git a/src/WebExpress.WebCore/WebSettingPage/ISettingCategoryContext.cs b/src/WebExpress.WebCore/WebSettingPage/ISettingCategoryContext.cs new file mode 100644 index 0000000..c0ea94c --- /dev/null +++ b/src/WebExpress.WebCore/WebSettingPage/ISettingCategoryContext.cs @@ -0,0 +1,48 @@ +using WebExpress.WebCore.WebApplication; +using WebExpress.WebCore.WebComponent; +using WebExpress.WebCore.WebIcon; +using WebExpress.WebCore.WebPlugin; + +namespace WebExpress.WebCore.WebSettingPage +{ + /// + /// Provides context for setting categories within the web setting page. + /// + public interface ISettingCategoryContext : IContext + { + /// + /// Returns the associated plugin context. + /// + IPluginContext PluginContext { get; } + + /// + /// Returns the corresponding application context. + /// + IApplicationContext ApplicationContext { get; } + + /// + /// Returns the category id. + /// + IComponentId CategoryId { get; } + + /// + /// Returns the icon. + /// + IIcon Icon { get; } + + /// + /// Returns the name. + /// + string Name { get; } + + /// + /// Returns the description. + /// + string Description { get; } + + /// + /// Returns the section. + /// + SettingSection Section { get; } + } +} diff --git a/src/WebExpress.WebCore/WebSettingPage/ISettingGroup.cs b/src/WebExpress.WebCore/WebSettingPage/ISettingGroup.cs new file mode 100644 index 0000000..cc2ebc9 --- /dev/null +++ b/src/WebExpress.WebCore/WebSettingPage/ISettingGroup.cs @@ -0,0 +1,9 @@ +namespace WebExpress.WebCore.WebSettingPage +{ + /// + /// Represents a group of settings in the web application. + /// + public interface ISettingGroup + { + } +} diff --git a/src/WebExpress.WebCore/WebSettingPage/ISettingGroupContext.cs b/src/WebExpress.WebCore/WebSettingPage/ISettingGroupContext.cs new file mode 100644 index 0000000..56949a2 --- /dev/null +++ b/src/WebExpress.WebCore/WebSettingPage/ISettingGroupContext.cs @@ -0,0 +1,53 @@ +using WebExpress.WebCore.WebApplication; +using WebExpress.WebCore.WebComponent; +using WebExpress.WebCore.WebIcon; +using WebExpress.WebCore.WebPlugin; + +namespace WebExpress.WebCore.WebSettingPage +{ + /// + /// Provides the context for a setting group. + /// + public interface ISettingGroupContext : IContext + { + /// + /// Returns the associated plugin context. + /// + IPluginContext PluginContext { get; } + + /// + /// Returns the corresponding application context. + /// + IApplicationContext ApplicationContext { get; } + + /// + /// Returns the setting category context to which the setting group belongs. + /// + ISettingCategoryContext SettingCategory { get; } + + /// + /// Returns the group id. + /// + IComponentId GroupId { get; } + + /// + /// Returns the icon. + /// + IIcon Icon { get; } + + /// + /// Returns the name. + /// + string Name { get; } + + /// + /// Returns the description. + /// + string Description { get; } + + /// + /// Returns the section. + /// + SettingSection Section { get; } + } +} diff --git a/src/WebExpress.WebCore/WebSettingPage/ISettingPage.cs b/src/WebExpress.WebCore/WebSettingPage/ISettingPage.cs new file mode 100644 index 0000000..3bc3b85 --- /dev/null +++ b/src/WebExpress.WebCore/WebSettingPage/ISettingPage.cs @@ -0,0 +1,26 @@ +using WebExpress.WebCore.WebEndpoint; +using WebExpress.WebCore.WebPage; + +namespace WebExpress.WebCore.WebSettingPage +{ + /// + /// Defines the contract for a setting page resource. + /// + public interface ISettingPage : ISettingPage + { + } + + /// + /// Defines the contract for a setting page resource that can be rendered using a specific context. + /// + /// The type of the visual tree. + public interface ISettingPage : IEndpoint where TVisualTree : IVisualTree + { + /// + /// Processing of the page. + /// + /// The context for rendering the setting page. + /// The visual tree to be rendered. + void Process(IRenderContext renderContext, TVisualTree visualTree); + } +} diff --git a/src/WebExpress.WebCore/WebSettingPage/ISettingPageContext.cs b/src/WebExpress.WebCore/WebSettingPage/ISettingPageContext.cs new file mode 100644 index 0000000..796801b --- /dev/null +++ b/src/WebExpress.WebCore/WebSettingPage/ISettingPageContext.cs @@ -0,0 +1,37 @@ +using WebExpress.WebCore.WebIcon; +using WebExpress.WebCore.WebPage; + +namespace WebExpress.WebCore.WebSettingPage +{ + /// + /// Interface representing the context of a setting page. + /// Provides access to plugin context, application context, conditions for activation, and caching behavior. + /// + public interface ISettingPageContext : IPageContext + { + /// + /// Returns the icon. + /// + IIcon Icon { get; } + + /// + /// Returns the setting category context to which the setting page belongs. + /// + ISettingCategoryContext SettingCategory { get; } + + /// + /// Returns the group context to which the setting page belongs. + /// + ISettingGroupContext SettingGroup { get; } + + /// + /// Returns the section to which the setting page belongs. + /// + SettingSection Section { get; } + + /// + /// Returns a value indicating whether the page should be displayed or hidden. + /// + bool Hide { get; } + } +} diff --git a/src/WebExpress.WebCore/WebSettingPage/ISettingPageManager.cs b/src/WebExpress.WebCore/WebSettingPage/ISettingPageManager.cs new file mode 100644 index 0000000..3b8a888 --- /dev/null +++ b/src/WebExpress.WebCore/WebSettingPage/ISettingPageManager.cs @@ -0,0 +1,92 @@ +using System; +using System.Collections.Generic; +using WebExpress.WebCore.WebApplication; +using WebExpress.WebCore.WebComponent; + +namespace WebExpress.WebCore.WebSettingPage +{ + /// + /// Interface for managing setting pages. + /// + public interface ISettingPageManager : IComponentManager + { + /// + /// An event that fires when an setting page is added. + /// + event EventHandler AddSettingPage; + + /// + /// An event that fires when an setting page is removed. + /// + event EventHandler RemoveSettingPage; + + /// + /// Returns the collection of setting categories. + /// + IEnumerable SettingCategories { get; } + + /// + /// Returns the collection of setting groups. + /// + IEnumerable SettingGroups { get; } + + /// + /// Returns the collection of setting pages. + /// + IEnumerable SettingPages { get; } + + /// + /// Returns an enumeration of setting page contextes. + /// + /// The setting page type. + /// An enumeration of setting page contextes. + IEnumerable GetSettingPages(Type settingPageType); + + /// + /// Returns an enumeration of setting page contextes. + /// + /// The setting page type. + /// The context of the application. + /// An enumeration of setting page contextes. + IEnumerable GetSettingPages(Type settingPageType, IApplicationContext applicationContext); + + /// + /// Returns an enumeration of setting page contexts for the specified application context and category. + /// + /// The context of the application. + /// The category for which to retrieve setting pages. + /// An enumeration of setting page contexts. + IEnumerable GetSettingPages(IApplicationContext applicationContext, ISettingCategoryContext category); + + /// + /// Returns an enumeration of setting page contexts for the specified application context, category, and group. + /// + /// The context of the application. + /// The group for which to retrieve setting pages. + /// An enumeration of setting page contexts. + IEnumerable GetSettingPages(IApplicationContext applicationContext, ISettingGroupContext group); + + /// + /// Returns the first setting page context for the specified application context and category. + /// + /// The context of the application. + /// The category for which to retrieve setting pages. + /// The first setting page context or null. + ISettingPageContext GetFirstSettingPage(IApplicationContext applicationContext, ISettingCategoryContext category); + + /// + /// Returns the categories associated with the specified application context. + /// + /// The context of the application. + /// An enumeration of category contexts associated with the specified application context. + IEnumerable GetSettingCategories(IApplicationContext applicationContext); + + /// + /// Returns the groups associated with the specified application context and category. + /// + /// The context of the application. + /// The category for which to retrieve groups. + /// An enumeration of setting group contexts associated with the provided application context and category. + IEnumerable GetSettingGroups(IApplicationContext applicationContext, ISettingCategoryContext category); + } +} \ No newline at end of file diff --git a/src/WebExpress.WebCore/WebSettingPage/Model/SettingCategoryDictionary.cs b/src/WebExpress.WebCore/WebSettingPage/Model/SettingCategoryDictionary.cs new file mode 100644 index 0000000..9b27e21 --- /dev/null +++ b/src/WebExpress.WebCore/WebSettingPage/Model/SettingCategoryDictionary.cs @@ -0,0 +1,116 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using WebExpress.WebCore.WebApplication; +using WebExpress.WebCore.WebPlugin; + +namespace WebExpress.WebCore.WebSettingPage.Model +{ + /// + /// Represents a dictionary for managing setting categories, organized by plugin and application contexts. + /// + internal class SettingCategoryDictionary + { + private readonly Dictionary>> _dict = []; + + /// + /// Returns the collection of setting categories. + /// + public IEnumerable All => _dict.Values + .SelectMany(a => a.Values) + .SelectMany(x => x) + .Select(x => x.SettingCategoryContext); + + /// + /// Adds a settings category. + /// + /// The settings category to insert. + /// True if the settings category was added successfully; otherwise, false. + public bool AddSettingCategoryItem(SettingCategoryItem item) + { + if (!_dict.TryGetValue(item.PluginContext, out var appDict)) + { + appDict = []; + _dict.Add(item.PluginContext, appDict); + } + + if (!appDict.TryGetValue(item.ApplicationContext, out var list)) + { + list = []; + appDict.Add(item.ApplicationContext, list); + } + + list.Add(item); + + return true; + } + + /// + /// Removes all elements associated with the specified plugin context. + /// + /// The context of the plugin that contains the elements to remove. + public void Remove(IPluginContext pluginContext) + { + _dict.Remove(pluginContext); + } + + /// + /// Removes all setting categories associated with the specified application context. + /// + /// The context of the application that contains the fragments to remove. + /// An enumerable collection of setting categories contexts that were removed. + public IEnumerable Remove(IApplicationContext applicationContext) + { + if (applicationContext == null) + { + yield break; + } + + foreach (var pluginDict in _dict.Values) + { + var removed = pluginDict + .Where(x => x.Key == applicationContext) + .SelectMany(x => x.Value) + .ToList(); + + pluginDict.Remove(applicationContext); + + foreach (var category in removed) + { + yield return category.SettingCategoryContext; + } + } + } + + /// + /// Returns the setting category context for the specified application context. + /// + /// The application context. + /// An enumeration of category contexts associated with the specified application context. + public IEnumerable GetSettingCategories(IApplicationContext applicationContext) + { + return _dict.Values + .SelectMany(x => x) + .Where(x => x.Key == applicationContext) + .SelectMany(x => x.Value) + .Select(x => x.SettingCategoryContext); + } + + /// + /// Returns the setting category context for the specified application context and category type. + /// + /// The application context. + /// The type of the category. + /// >The setting category context if found; otherwise, null. + public ISettingCategoryContext GetSettingCategory(IApplicationContext applicationContext, Type categoryType) + { + return _dict.Values + .SelectMany(x => x) + .Where(x => x.Key == applicationContext) + .SelectMany(x => x.Value) + .Where(x => x.SettingCategoryClass == categoryType) + .Select(x => x.SettingCategoryContext) + .FirstOrDefault(); + } + } +} diff --git a/src/WebExpress.WebCore/WebSettingPage/Model/SettingCategoryItem.cs b/src/WebExpress.WebCore/WebSettingPage/Model/SettingCategoryItem.cs new file mode 100644 index 0000000..26b38e7 --- /dev/null +++ b/src/WebExpress.WebCore/WebSettingPage/Model/SettingCategoryItem.cs @@ -0,0 +1,55 @@ +using System; +using WebExpress.WebCore.WebApplication; +using WebExpress.WebCore.WebPlugin; + +namespace WebExpress.WebCore.WebSettingPage.Model +{ + /// + /// Represents an item on the setting category. + /// + public class SettingCategoryItem : IDisposable + { + /// + /// Returns the context of the associated plugin. + /// + public IPluginContext PluginContext { get; internal set; } + + /// + /// Returns the application context. + /// + public IApplicationContext ApplicationContext { get; internal set; } + + /// + /// Returns the setting category context. + /// + public ISettingCategoryContext SettingCategoryContext { get; internal set; } + + /// + /// Returns the class type of the setting category. + /// + public Type SettingCategoryClass { get; internal set; } + + /// + /// Returns the human-readable name or a internationalization key of the category. + /// + public string Name { get; internal set; } + + /// + /// Returns the human-readable description or a internationalization key of the category. + /// + public string Description { get; internal set; } + + /// + /// Returns the section. + /// + public SettingSection Section { get; internal set; } + + /// + /// Release of unmanaged resources reserved during use. + /// + public void Dispose() + { + GC.SuppressFinalize(this); + } + } +} diff --git a/src/WebExpress.WebCore/WebSettingPage/Model/SettingGroupDictionary.cs b/src/WebExpress.WebCore/WebSettingPage/Model/SettingGroupDictionary.cs new file mode 100644 index 0000000..d46cb95 --- /dev/null +++ b/src/WebExpress.WebCore/WebSettingPage/Model/SettingGroupDictionary.cs @@ -0,0 +1,132 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using WebExpress.WebCore.WebApplication; +using WebExpress.WebCore.WebPlugin; + +namespace WebExpress.WebCore.WebSettingPage.Model +{ + /// + /// Represents a dictionary for managing setting groups, organized by plugin and application contexts. + /// + internal class SettingGroupDictionary + { + private readonly Dictionary>> _dict = new(); + + /// + /// Returns the collection of setting groups. + /// + public IEnumerable All => _dict.Values + .SelectMany(a => a.Values) + .SelectMany(x => x) + .Select(x => x.SettingGroupContext); + + /// + /// Adds a settings group. + /// + /// The settings group to insert. + /// True if the settings group was added successfully; otherwise, false. + public bool AddSettingGroupItem(SettingGroupItem item) + { + if (!_dict.TryGetValue(item.PluginContext, out var appDict)) + { + appDict = []; + _dict.Add(item.PluginContext, appDict); + } + + if (!appDict.TryGetValue(item.ApplicationContext, out var list)) + { + list = []; + appDict.Add(item.ApplicationContext, list); + } + + list.Add(item); + + return true; + } + + /// + /// Removes all elements associated with the specified plugin context. + /// + /// The context of the plugin that contains the elements to remove. + public void Remove(IPluginContext pluginContext) + { + _dict.Remove(pluginContext); + } + + /// + /// Removes all setting groups associated with the specified application context. + /// + /// The context of the application that contains the fragments to remove. + /// An enumerable collection of setting groups contexts that were removed. + public IEnumerable Remove(IApplicationContext applicationContext) + { + if (applicationContext == null) + { + yield break; + } + + foreach (var pluginDict in _dict.Values) + { + var removed = pluginDict + .Where(x => x.Key == applicationContext) + .SelectMany(x => x.Value) + .ToList(); + + pluginDict.Remove(applicationContext); + + foreach (var category in removed) + { + yield return category.SettingGroupContext; + } + } + } + + /// + /// Returns the setting group context for the specified application context and group type. + /// + /// The application context. + /// An enumeration of category contexts associated with the specified application context. + public IEnumerable GetSettingGroups(IApplicationContext applicationContext) + { + return _dict.Values + .SelectMany(x => x) + .Where(x => x.Key == applicationContext) + .SelectMany(x => x.Value) + .Select(x => x.SettingGroupContext); + } + + /// + /// Returns the setting group context for the specified application context and group type. + /// + /// The application context. + /// The category context. + /// An enumeration of category contexts associated with the specified application context. + public IEnumerable GetSettingGroups(IApplicationContext applicationContext, ISettingCategoryContext categoryContext) + { + return _dict.Values + .SelectMany(x => x) + .Where(x => x.Key == applicationContext) + .SelectMany(x => x.Value) + .Where(x => x.SettingGroupContext.SettingCategory == categoryContext) + .Select(x => x.SettingGroupContext); + } + + /// + /// Returns the setting group context for the specified application context and group type. + /// + /// The application context. + /// The type of the group. + /// The setting group context if found; otherwise, null. + public ISettingGroupContext GetSettingGroup(IApplicationContext applicationContext, Type groupType) + { + return _dict.Values + .SelectMany(x => x) + .Where(x => x.Key == applicationContext) + .SelectMany(x => x.Value) + .Where(x => x.SettingGroupClass == groupType) + .Select(x => x.SettingGroupContext) + .FirstOrDefault(); + } + } +} diff --git a/src/WebExpress.WebCore/WebSettingPage/Model/SettingGroupItem.cs b/src/WebExpress.WebCore/WebSettingPage/Model/SettingGroupItem.cs new file mode 100644 index 0000000..ab87a25 --- /dev/null +++ b/src/WebExpress.WebCore/WebSettingPage/Model/SettingGroupItem.cs @@ -0,0 +1,60 @@ +using System; +using WebExpress.WebCore.WebApplication; +using WebExpress.WebCore.WebPlugin; + +namespace WebExpress.WebCore.WebSettingPage.Model +{ + /// + /// Represents an item on the setting group. + /// + public class SettingGroupItem : IDisposable + { + /// + /// Returns the context of the associated plugin. + /// + public IPluginContext PluginContext { get; internal set; } + + /// + /// Returns the application context. + /// + public IApplicationContext ApplicationContext { get; internal set; } + + /// + /// Returns the setting group context. + /// + public ISettingGroupContext SettingGroupContext { get; internal set; } + + /// + /// Returns the class type of the setting group. + /// + public Type SettingGroupClass { get; internal set; } + + /// + /// Returns the human-readable name or a internationalization key of the group. + /// + public string Name { get; internal set; } + + /// + /// Returns the human-readable description or a internationalization key of the group. + /// + public string Description { get; internal set; } + + /// + /// Returns the setting category. + /// + public Type Category { get; internal set; } + + /// + /// Returns the section. + /// + public SettingSection Section { get; internal set; } + + /// + /// Release of unmanaged resources reserved during use. + /// + public void Dispose() + { + GC.SuppressFinalize(this); + } + } +} diff --git a/src/WebExpress.WebCore/WebSettingPage/Model/SettingPageDictionary.cs b/src/WebExpress.WebCore/WebSettingPage/Model/SettingPageDictionary.cs new file mode 100644 index 0000000..cbbbcf2 --- /dev/null +++ b/src/WebExpress.WebCore/WebSettingPage/Model/SettingPageDictionary.cs @@ -0,0 +1,205 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using WebExpress.WebCore.WebApplication; +using WebExpress.WebCore.WebComponent; +using WebExpress.WebCore.WebEndpoint; +using WebExpress.WebCore.WebPlugin; + +namespace WebExpress.WebCore.WebSettingPage.Model +{ + /// + /// Represents a dictionary for managing setting pages, organized by plugin and application contexts. + /// + internal class SettingPageDictionary + { + private readonly Dictionary>> _dict = []; + + /// + /// Returns the collection of setting pages. + /// + public IEnumerable All => _dict.Values + .SelectMany(a => a.Values) + .SelectMany(x => x) + .Select(x => x.SettingPageContext); + + /// + /// Adds a settings page. + /// + /// The settings page to insert. + /// True if the settings page was added successfully; otherwise, false. + public bool AddSettingPageItem(SettingPageItem item) + { + if (!_dict.TryGetValue(item.PluginContext, out var appDict)) + { + appDict = []; + _dict.Add(item.PluginContext, appDict); + } + + if (!appDict.TryGetValue(item.ApplicationContext, out var list)) + { + list = []; + appDict.Add(item.ApplicationContext, list); + } + + list.Add(item); + + return true; + } + + /// + /// Removes all elemets associated with the specified plugin context. + /// + /// The context of the plugin that contains the elemets to remove. + public void Remove(IPluginContext pluginContext) + { + _dict.Remove(pluginContext); + } + + /// + /// Removes all setting pages associated with the specified application context. + /// + /// The context of the application that contains the fragments to remove. + /// An enumerable collection of setting page contexts that were removed. + public IEnumerable Remove(IApplicationContext applicationContext) + { + if (applicationContext == null) + { + yield break; + } + + foreach (var pluginDict in _dict.Values) + { + var removed = pluginDict + .Where(x => x.Key == applicationContext) + .SelectMany(x => x.Value) + .ToList(); + + pluginDict.Remove(applicationContext); + + foreach (var page in removed) + { + yield return page.SettingPageContext; + } + } + } + + /// + /// Returns an enumeration of setting page contextes. + /// + /// The setting page type. + /// An enumeration of setting page contextes. + public IEnumerable GetSettingPages(Type settingPageType) + { + return _dict.Values + .SelectMany(a => a.Values) + .SelectMany(x => x) + .Where(x => x.SettingPageClass.Equals(settingPageType)) + .Select(x => x.SettingPageContext); + } + + /// + /// Returns an enumeration of setting page contextes. + /// + /// The setting page type. + /// The context of the application. + /// An enumeration of setting page contextes. + public IEnumerable GetSettingPages(Type settingPageType, IApplicationContext applicationContext) + { + return _dict.Values + .SelectMany(a => a) + .Where(a => a.Key.Equals(applicationContext)) + .SelectMany(x => x.Value) + .Where(x => x.SettingPageClass.Equals(settingPageType)) + .Select(x => x.SettingPageContext); + } + + /// + /// Determines whether the dictionary contains the specified plugin context. + /// + /// The context of the plugin to locate in the dictionary. + /// True if the dictionary contains an element with the specified plugin context; otherwise, false. + public bool Contains(IPluginContext pluginContext) + { + return _dict.ContainsKey(pluginContext); + } + + /// + /// Determines whether the dictionary contains the specified apllication context. + /// + /// The context of the application to locate in the dictionary. + /// True if the dictionary contains an element with the specified application context; otherwise, false. + public bool Contains(IApplicationContext applicationContext) + { + return _dict.Values.Any(x => x.ContainsKey(applicationContext)); + } + + /// + /// Creates a new setting page instance or returns an existing cached instance. + /// + /// The context used for setting page creation. + /// The central management hub for components. + /// The context of the HTTP server. + /// The created or cached setting page instance. + public IEndpoint CreateSettingPageInstance(ISettingPageContext settingPageContext, IComponentHub componentHub, IHttpServerContext httpServerContext) + { + var settingPageItem = _dict.Values + .SelectMany(a => a.Values) + .SelectMany(x => x) + .FirstOrDefault(x => x.SettingPageContext.Equals(settingPageContext)); + + if (settingPageItem != null && settingPageItem.Instance == null) + { + var instance = ComponentActivator.CreateInstance + ( + settingPageItem.SettingPageClass, + settingPageContext, + httpServerContext, + componentHub, + settingPageContext.ApplicationContext + ); + + if (settingPageItem.Cache) + { + settingPageItem.Instance = instance; + } + + return instance; + } + + return settingPageItem?.Instance; + } + + /// + /// Returns an enumeration of setting page contexts for the specified application context and category. + /// + /// The context of the application. + /// The category for which to retrieve setting pages. + /// An enumeration of setting page contexts. + public IEnumerable GetSettingPages(IApplicationContext applicationContext, ISettingCategoryContext categoryContext) + { + return _dict.Values + .SelectMany(a => a) + .Where(a => a.Key == applicationContext) + .SelectMany(x => x.Value) + .Where(x => x.SettingPageContext?.SettingCategory == categoryContext) + .Select(x => x.SettingPageContext); + } + + /// + /// Returns an enumeration of setting page contexts for the specified application context, category, and group. + /// + /// The context of the application. + /// The group for which to retrieve setting pages. + /// An enumeration of setting page contexts. + public IEnumerable GetSettingPages(IApplicationContext applicationContext, ISettingGroupContext groupContext) + { + return _dict.Values + .SelectMany(a => a) + .Where(a => a.Key == applicationContext) + .SelectMany(x => x.Value) + .Where(x => x.SettingPageContext?.SettingGroup == groupContext) + .Select(x => x.SettingPageContext); + } + } +} diff --git a/src/WebExpress.WebCore/WebSettingPage/Model/SettingPageItem.cs b/src/WebExpress.WebCore/WebSettingPage/Model/SettingPageItem.cs new file mode 100644 index 0000000..ee6f3b3 --- /dev/null +++ b/src/WebExpress.WebCore/WebSettingPage/Model/SettingPageItem.cs @@ -0,0 +1,136 @@ +using System; +using System.Collections.Generic; +using WebExpress.WebCore.WebApplication; +using WebExpress.WebCore.WebComponent; +using WebExpress.WebCore.WebCondition; +using WebExpress.WebCore.WebEndpoint; +using WebExpress.WebCore.WebIcon; +using WebExpress.WebCore.WebPlugin; +using WebExpress.WebCore.WebUri; + +namespace WebExpress.WebCore.WebSettingPage.Model +{ + /// + /// Represents an item on the setting page. + /// + public class SettingPageItem : IDisposable + { + /// + /// Returns the endpoint id. + /// + public IComponentId EndpointId { get; internal set; } + + /// + /// Returns the context of the associated plugin. + /// + public IPluginContext PluginContext { get; internal set; } + + /// + /// Returns the application context. + /// + public IApplicationContext ApplicationContext { get; internal set; } + + /// + /// Returns the setting page context. + /// + public ISettingPageContext SettingPageContext { get; internal set; } + + /// + /// Returns the class type of the setting page. + /// + public Type SettingPageClass { get; internal set; } + + /// + /// Returns the instance of the setting page, if the page is cached, otherwise null. + /// + public IEndpoint Instance { get; internal set; } + + /// + /// Returns or sets the parent type. + /// + public Type ParentType { get; set; } + + /// + /// Returns or sets the paths of the resource. + /// + public UriEndpoint ContextPath { get; set; } + + /// + /// Returns or sets the path segment. + /// + public IUriPathSegment PathSegment { get; internal set; } + + /// + /// Returns the group type. + /// + public Type SettingGroupType { get; internal set; } + + /// + /// Returns the section. + /// + public SettingSection Section { get; internal set; } + + /// + /// Returns a value indicating whether the component is created once and reused on each execution. + /// + public bool Cache { get; internal set; } + + /// + /// Returns the icon. + /// + public IIcon Icon { get; internal set; } + + /// + /// Returns the setting page title. + /// + public string PageTitle { get; internal set; } + + /// + /// Returns a value indicating whether the page should be displayed or hidden. + /// + public bool Hide { get; internal set; } + + /// + /// Returns or sets whether all subpaths should be taken into sitemap. + /// + public bool IncludeSubPaths { get; internal set; } + + /// + /// Returns the attributes associated with the page. + /// + public IEnumerable Attributes { get; internal set; } + + /// + /// Returns the scope names that provides the resource. The scope name + /// is a string with a name (e.g. global, admin), which can be used by elements to + /// determine whether content and how content should be displayed. + /// + public IEnumerable Scopes { get; internal set; } = []; + + /// + /// Returns the conditions that must be met for the resource to be active. + /// + public IEnumerable Conditions { get; internal set; } = []; + + /// + /// Returns the group context to which the setting page belongs. + /// + public ISettingGroupContext SettingGroup { get; internal set; } + + /// + /// Initializes a new instance of the class. + /// + /// The endpoint manager responsible for managing endpoints. + internal SettingPageItem(IEndpointManager endpointManager) + { + } + + /// + /// Release of unmanaged resources reserved during use. + /// + public void Dispose() + { + GC.SuppressFinalize(this); + } + } +} diff --git a/src/WebExpress.WebCore/WebSettingPage/Model/TimeSpanConverter.cs b/src/WebExpress.WebCore/WebSettingPage/Model/TimeSpanConverter.cs new file mode 100644 index 0000000..47d58b7 --- /dev/null +++ b/src/WebExpress.WebCore/WebSettingPage/Model/TimeSpanConverter.cs @@ -0,0 +1,52 @@ +using System; + +namespace WebExpress.WebCore.WebSettingPage.Model +{ + + /// + /// Converts a TimeSpan object to a formatted string and vice versa. + /// + public class TimeSpanConverter + { + /// + /// Converts a TimeSpan object to a formatted string. + /// + /// The TimeSpan object to convert. + /// The type to convert to. + /// Optional parameter for conversion. + /// The language to use in the converter. + /// A formatted string representing the TimeSpan. + public object Convert(object value, Type targetType, object parameter, string language) + { + if (value == null) + { + return null; + } + + var ts = TimeSpan.Parse(value.ToString()); + return string.Format + ( + "{0}d {1:D2}h {2:D2}m {3:D2}s {4:D2}ms", + ts.Days, + ts.Hours, + ts.Minutes, + ts.Seconds, + ts.Milliseconds + ); + } + + /// + /// Converts a formatted string back to a TimeSpan object. + /// + /// The formatted string to convert. + /// The type to convert to. + /// Optional parameter for conversion. + /// The language to use in the converter. + /// The TimeSpan object. + /// Thrown when the method is not implemented. + public object ConvertBack(object value, Type targetType, object parameter, string language) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/WebExpress.WebCore/WebSettingPage/SettingCategoryContext.cs b/src/WebExpress.WebCore/WebSettingPage/SettingCategoryContext.cs new file mode 100644 index 0000000..dee5991 --- /dev/null +++ b/src/WebExpress.WebCore/WebSettingPage/SettingCategoryContext.cs @@ -0,0 +1,57 @@ +using WebExpress.WebCore.WebApplication; +using WebExpress.WebCore.WebComponent; +using WebExpress.WebCore.WebIcon; +using WebExpress.WebCore.WebPlugin; + +namespace WebExpress.WebCore.WebSettingPage +{ + /// + /// Provides context for setting categories within the web setting page. + /// + public class SettingCategoryContext : ISettingCategoryContext + { + /// + /// Returns the associated plugin context. + /// + public IPluginContext PluginContext { get; internal set; } + + /// + /// Returns the corresponding application context. + /// + public IApplicationContext ApplicationContext { get; internal set; } + + /// + /// Returns the category id. + /// + public IComponentId CategoryId { get; internal set; } + + /// + /// Returns the icon. + /// + public IIcon Icon { get; internal set; } + + /// + /// Returns the name. + /// + public string Name { get; internal set; } + + /// + /// Returns the description. + /// + public string Description { get; internal set; } + + /// + /// Returns the section. + /// + public SettingSection Section { get; internal set; } + + /// + /// Returns a string that represents the current object. + /// + /// A string that represents the current object. + public override string ToString() + { + return $"Setting category: {CategoryId}"; + } + } +} diff --git a/src/WebExpress.WebCore/WebSettingPage/SettingGroupContext.cs b/src/WebExpress.WebCore/WebSettingPage/SettingGroupContext.cs new file mode 100644 index 0000000..591f0f5 --- /dev/null +++ b/src/WebExpress.WebCore/WebSettingPage/SettingGroupContext.cs @@ -0,0 +1,62 @@ +using WebExpress.WebCore.WebApplication; +using WebExpress.WebCore.WebComponent; +using WebExpress.WebCore.WebIcon; +using WebExpress.WebCore.WebPlugin; + +namespace WebExpress.WebCore.WebSettingPage +{ + /// + /// Provides context for setting group within the web setting page. + /// + public class SettingGroupContext : ISettingGroupContext + { + /// + /// Returns the associated plugin context. + /// + public IPluginContext PluginContext { get; internal set; } + + /// + /// Returns the corresponding application context. + /// + public IApplicationContext ApplicationContext { get; internal set; } + + /// + /// Returns the setting category context to which the setting group belongs. + /// + public ISettingCategoryContext SettingCategory { get; internal set; } + + /// + /// Returns the group id. + /// + public IComponentId GroupId { get; internal set; } + + /// + /// Returns the icon. + /// + public IIcon Icon { get; internal set; } + + /// + /// Returns the name. + /// + public string Name { get; internal set; } + + /// + /// Returns the description. + /// + public string Description { get; internal set; } + + /// + /// Returns the section. + /// + public SettingSection Section { get; internal set; } + + /// + /// Returns a string that represents the current object. + /// + /// A string that represents the current object. + public override string ToString() + { + return $"Setting group: {GroupId}"; + } + } +} diff --git a/src/WebExpress.WebCore/WebSettingPage/SettingPageContext.cs b/src/WebExpress.WebCore/WebSettingPage/SettingPageContext.cs new file mode 100644 index 0000000..f429492 --- /dev/null +++ b/src/WebExpress.WebCore/WebSettingPage/SettingPageContext.cs @@ -0,0 +1,45 @@ +using WebExpress.WebCore.WebIcon; +using WebExpress.WebCore.WebPage; + +namespace WebExpress.WebCore.WebSettingPage +{ + /// + /// Interface representing the context of a setting page. + /// Provides access to plugin context, application context, conditions for activation, and caching behavior. + /// + public class SettingPageContext : PageContext, ISettingPageContext + { + /// + /// Returns the icon. + /// + public IIcon Icon { get; internal set; } + + /// + /// Returns the setting category context to which the setting page belongs. + /// + public ISettingCategoryContext SettingCategory => SettingGroup?.SettingCategory; + + /// + /// Returns the group context to which the setting page belongs. + /// + public ISettingGroupContext SettingGroup { get; internal set; } + + /// + /// Returns the section of the setting page. + /// + public SettingSection Section { get; internal set; } + + /// + /// Returns a value indicating whether the page should be displayed or hidden. + /// + public bool Hide { get; internal set; } + + /// + /// Initializes a new instance of the class with the specified parent type and context path. + /// + public SettingPageContext() + : base() + { + } + } +} diff --git a/src/WebExpress.WebCore/WebSettingPage/SettingPageManager.cs b/src/WebExpress.WebCore/WebSettingPage/SettingPageManager.cs new file mode 100644 index 0000000..c486b21 --- /dev/null +++ b/src/WebExpress.WebCore/WebSettingPage/SettingPageManager.cs @@ -0,0 +1,803 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using WebExpress.WebCore.Internationalization; +using WebExpress.WebCore.WebApplication; +using WebExpress.WebCore.WebAttribute; +using WebExpress.WebCore.WebComponent; +using WebExpress.WebCore.WebCondition; +using WebExpress.WebCore.WebEndpoint; +using WebExpress.WebCore.WebIcon; +using WebExpress.WebCore.WebMessage; +using WebExpress.WebCore.WebPage; +using WebExpress.WebCore.WebPlugin; +using WebExpress.WebCore.WebScope; +using WebExpress.WebCore.WebSettingPage.Model; + +namespace WebExpress.WebCore.WebSettingPage +{ + /// + /// Management of settings pages. + /// + public sealed class SettingPageManager : ISettingPageManager + { + private readonly IComponentHub _componentHub; + private readonly IHttpServerContext _httpServerContext; + private readonly SettingCategoryDictionary _categoryDictionary = new(); + private readonly SettingGroupDictionary _groupDictionary = new(); + private readonly SettingPageDictionary _pageDictionary = new(); + private static readonly Dictionary _delegateCache = []; + + /// + /// An event that fires when an setting page is added. + /// + public event EventHandler AddSettingPage; + + /// + /// An event that fires when an setting page is removed. + /// + public event EventHandler RemoveSettingPage; + + /// + /// An event that fires when an setting category is added. + /// + public event EventHandler AddSettingCategory; + + /// + /// An event that fires when an setting category is removed. + /// + public event EventHandler RemoveSettingCategory; + + /// + /// An event that fires when an setting group is added. + /// + public event EventHandler AddSettingGroup; + + /// + /// An event that fires when an setting group is removed. + /// + public event EventHandler RemoveSettingGroup; + + /// + /// Returns the collection of setting categories. + /// + public IEnumerable SettingCategories => _categoryDictionary.All; + + /// + /// Returns the collection of setting groups. + /// + public IEnumerable SettingGroups => _groupDictionary.All; + + /// + /// Returns the collection of setting pages. + /// + public IEnumerable SettingPages => _pageDictionary.All; + + /// + /// Initializes a new instance of the class. + /// + /// The component hub. + /// The reference to the context of the host. + [SuppressMessage("CodeQuality", "IDE0051:Remove unused private members", Justification = "Used via Reflection.")] + private SettingPageManager(IComponentHub componentHub, IHttpServerContext httpServerContext) + { + _componentHub = componentHub; + _httpServerContext = httpServerContext; + + _componentHub.PluginManager.AddPlugin += OnAddPlugin; + _componentHub.PluginManager.RemovePlugin += OnRemovePlugin; + _componentHub.ApplicationManager.AddApplication += OnAddApplication; + _componentHub.ApplicationManager.RemoveApplication += OnRemoveApplication; + + var endpointtRegistration = new EndpointRegistration() + { + EndpointResolver = (type, applicationContext) => applicationContext != null ? GetSettingPages(type, applicationContext) : GetSettingPages(type), + EndpointsResolver = () => SettingPages, + HandleRequest = (request, endpontContext) => + { + var pageInstance = CreateSettingPageInstance(endpontContext as ISettingPageContext); + var pageType = pageInstance.GetType(); + var pageContext = endpontContext as IPageContext; + var renderContext = new RenderContext(pageInstance, pageContext, request); + var visualTreeContext = new VisualTreeContext(renderContext); + + var visualTreeType = pageType.GetInterface(typeof(ISettingPage<>).Name).GetGenericArguments()[0]; + if (!_delegateCache.TryGetValue(pageType, out var del)) + { + // create and compile the expression + var renderContextParam = Expression.Parameter(typeof(IRenderContext), "renderContext"); + var visualTreeParam = Expression.Parameter(visualTreeType, "visualTree"); + var processMethod = pageType.GetMethod("Process", [typeof(IRenderContext), visualTreeType]); + var callProzessMethod = Expression.Call + ( + Expression.Constant(pageInstance), + processMethod, + renderContextParam, + visualTreeParam + ); + var lambda = Expression.Lambda(callProzessMethod, renderContextParam, visualTreeParam) + .Compile(); + + _delegateCache[pageType] = lambda; + del = lambda; + } + + // create visual tree instance + var visualTreeInstance = default(IVisualTree); + var flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; + var constructors = visualTreeType?.GetConstructors(flags); + + if (constructors != null) + { + foreach (var constructor in constructors.OrderByDescending(x => x.GetParameters().Length)) + { + // injection + var parameters = constructor.GetParameters(); + var hubProperties = _componentHub.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance); + var contextIdProperty = pageContext.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance) + .Where(x => x.PropertyType == typeof(IComponentId)) + .FirstOrDefault(); + + var parameterValues = parameters.Select(parameter => + parameter.ParameterType == typeof(IComponentHub) ? componentHub : + parameter.ParameterType == typeof(IHttpServerContext) ? httpServerContext : + parameter.ParameterType == typeof(IPageContext) ? pageContext : + parameter.ParameterType == typeof(IComponentId) ? contextIdProperty?.GetValue(pageContext) : + hubProperties.Where(x => x.PropertyType == parameter.ParameterType) + .FirstOrDefault()? + .GetValue(componentHub) ?? null + ).ToArray(); + + if (constructor.Invoke(parameterValues) is IVisualTree visualTree) + { + visualTreeInstance = visualTree; + } + } + } + else + { + visualTreeInstance = Activator.CreateInstance(); + } + + // execute the cached delegate + del.DynamicInvoke(renderContext, visualTreeInstance); + + return new ResponseOK() + { + Content = visualTreeInstance.Render(visualTreeContext) + }; + } + }; + + AddSettingPage += (sender, e) => endpointtRegistration.AddEndpoint?.Invoke(sender, e); + RemoveSettingPage += (sender, e) => endpointtRegistration.RemoveEndpoint?.Invoke(sender, e); + + _componentHub.EndpointManager.Register(endpointtRegistration); + + _httpServerContext.Log.Debug(I18N.Translate("webexpress.webapp:settingpagemanager.initialization")); + } + + /// + /// Creates a new setting page and returns it. If a page already exists (through caching), the existing instance is returned. + /// + /// The context used for setting page creation. + /// The created or cached page. + private IEndpoint CreateSettingPageInstance(ISettingPageContext settingPageContext) + { + return _pageDictionary.CreateSettingPageInstance(settingPageContext, _componentHub, _httpServerContext); + } + + /// + /// Discovers and binds setting pages to an application. + /// + /// The context of the plugin whose setting pages are to be associated. + private void Register(IPluginContext pluginContext) + { + if (_pageDictionary.Contains(pluginContext)) + { + return; + } + + RegisterCategory(pluginContext, _componentHub.ApplicationManager.GetApplications(pluginContext)); + RegisterGroup(pluginContext, _componentHub.ApplicationManager.GetApplications(pluginContext)); + RegisterPage(pluginContext, _componentHub.ApplicationManager.GetApplications(pluginContext)); + } + + /// + /// Discovers and binds setting pages to an application. + /// + /// The context of the application whose pages are to be associated. + private void Register(IApplicationContext applicationContext) + { + if (_pageDictionary.Contains(applicationContext)) + { + return; + } + + foreach (var pluginContext in _componentHub.PluginManager.GetPlugins(applicationContext)) + { + RegisterCategory(pluginContext, [applicationContext]); + RegisterGroup(pluginContext, [applicationContext]); + RegisterPage(pluginContext, [applicationContext]); + } + } + + /// + /// Registers categories for a given plugin and application context. + /// + /// The plugin context. + /// The application context (optional). + private void RegisterCategory(IPluginContext pluginContext, IEnumerable applicationContexts) + { + var assembly = pluginContext.Assembly; + + foreach (var settingCategoryType in assembly.GetTypes() + .Where(x => x.IsClass == true && x.IsSealed && x.IsPublic) + .Where(x => x.GetInterface(typeof(ISettingCategory).Name) != null)) + { + var id = settingCategoryType.FullName?.ToLower(); + var icon = default(IIcon); + var name = default(string); + var description = default(string); + var section = SettingSection.Primary; + + // determining attributes + foreach (var customAttribute in settingCategoryType.CustomAttributes + .Where(x => x.AttributeType.GetInterfaces().Contains(typeof(ISettingCategoryAttribute)))) + { + if (customAttribute.AttributeType.IsGenericType && customAttribute.AttributeType.GetGenericTypeDefinition() == typeof(WebIconAttribute<>)) + { + var type = customAttribute.AttributeType.GenericTypeArguments.FirstOrDefault(); + icon ??= Activator.CreateInstance(type) as IIcon; + } + else if (customAttribute.AttributeType == typeof(NameAttribute)) + { + name ??= customAttribute.ConstructorArguments.FirstOrDefault().Value?.ToString(); + } + else if (customAttribute.AttributeType == typeof(DescriptionAttribute)) + { + description ??= customAttribute.ConstructorArguments.FirstOrDefault().Value?.ToString(); + } + else if (customAttribute.AttributeType == typeof(SettingSectionAttribute)) + { + section = Enum.Parse(customAttribute.ConstructorArguments.FirstOrDefault().Value?.ToString()); + } + } + + // assign the category to existing applications + foreach (var applicationContext in applicationContexts) + { + var settingCategoryContext = new SettingCategoryContext() + { + CategoryId = new ComponentId(id), + ApplicationContext = applicationContext, + PluginContext = pluginContext, + Icon = icon, + Name = name, + Description = description, + Section = section + }; + + // create meta information of the setting category + var settingCategoryItem = new SettingCategoryItem() + { + PluginContext = pluginContext, + ApplicationContext = applicationContext, + SettingCategoryContext = settingCategoryContext, + SettingCategoryClass = settingCategoryType, + Name = name, + Description = description, + Section = section + }; + + // insert the settings category into the dictionary + if (_categoryDictionary.AddSettingCategoryItem(settingCategoryItem)) + { + OnAddSettingCategory(settingCategoryContext); + + _httpServerContext?.Log.Debug + ( + I18N.Translate + ( + "webexpress.webcore:settingpagemanager.register.category", + id, + applicationContext.ApplicationId + ) + ); + } + } + } + } + + /// + /// Registers groups for a given plugin and application context. + /// + /// The plugin context. + /// The application context (optional). + private void RegisterGroup(IPluginContext pluginContext, IEnumerable applicationContexts) + { + var assembly = pluginContext.Assembly; + + foreach (var settingGroupType in assembly.GetTypes() + .Where(x => x.IsClass == true && x.IsSealed && x.IsPublic) + .Where(x => x.GetInterface(typeof(ISettingGroup).Name) != null)) + { + var id = settingGroupType.FullName?.ToLower(); + var icon = default(IIcon); + var name = default(string); + var description = default(string); + var category = default(Type); + var section = SettingSection.Primary; + + // determining attributes + foreach (var customAttribute in settingGroupType.CustomAttributes + .Where(x => x.AttributeType.GetInterfaces().Contains(typeof(ISettingGroupAttribute)))) + { + if (customAttribute.AttributeType.IsGenericType && customAttribute.AttributeType.GetGenericTypeDefinition() == typeof(WebIconAttribute<>)) + { + var type = customAttribute.AttributeType.GenericTypeArguments.FirstOrDefault(); + icon ??= Activator.CreateInstance(type) as IIcon; + } + else if (customAttribute.AttributeType == typeof(NameAttribute)) + { + name = customAttribute.ConstructorArguments.FirstOrDefault().Value?.ToString(); + } + else if (customAttribute.AttributeType == typeof(DescriptionAttribute)) + { + description = customAttribute.ConstructorArguments.FirstOrDefault().Value?.ToString(); + } + else if (customAttribute.AttributeType.IsGenericType && customAttribute.AttributeType.GetGenericTypeDefinition() == typeof(SettingCategoryAttribute<>)) + { + category = customAttribute.AttributeType.GenericTypeArguments.FirstOrDefault(); + } + else if (customAttribute.AttributeType == typeof(SettingSectionAttribute)) + { + section = Enum.Parse(customAttribute.ConstructorArguments.FirstOrDefault().Value?.ToString()); + } + } + + if (category == default) + { + _httpServerContext?.Log.Warning + ( + I18N.Translate + ( + "webexpress.webcore:settingpagemanager.register.nocategory", + id + ) + ); + } + + // assign the group to existing applications + foreach (var applicationContext in applicationContexts) + { + var settingGroupContext = new SettingGroupContext() + { + GroupId = new ComponentId(id), + ApplicationContext = applicationContext, + PluginContext = pluginContext, + Icon = icon, + Name = name, + Description = description, + SettingCategory = _categoryDictionary.GetSettingCategory(applicationContext, category), + Section = section + }; + + // create meta information of the setting group + var settingGroupItem = new SettingGroupItem() + { + PluginContext = pluginContext, + ApplicationContext = applicationContext, + SettingGroupContext = settingGroupContext, + SettingGroupClass = settingGroupType, + Name = name, + Description = description, + Category = category?.GetType(), + Section = section + }; + + // insert the settings category into the dictionary + if (_groupDictionary.AddSettingGroupItem(settingGroupItem)) + { + OnAddSettingGroup(settingGroupContext); + + _httpServerContext?.Log.Debug + ( + I18N.Translate + ( + "webexpress.webcore:settingpagemanager.register.group", + id, + applicationContext.ApplicationId + ) + ); + } + } + } + } + + /// + /// Registers pages for a given plugin and application context. + /// + /// The plugin context. + /// The application context (optional). + private void RegisterPage(IPluginContext pluginContext, IEnumerable applicationContexts) + { + var assembly = pluginContext.Assembly; + + foreach (var settingPageType in assembly.GetTypes() + .Where(x => x.IsClass == true && x.IsSealed && x.IsPublic) + .Where(x => x.GetInterface(typeof(ISettingPage<>).Name) != null)) + { + var id = settingPageType.FullName?.ToLower(); + var title = settingPageType.Name; + var segment = default(ISegmentAttribute); + var conditions = new List(); + var contextPath = string.Empty; + var scopes = new List(); + var group = default(Type); + var section = SettingSection.Primary; + var includeSubPaths = false; + var hide = false; + var icon = default(IIcon); + var cache = false; + var attributes = settingPageType.CustomAttributes + .Where(x => !x.AttributeType.GetInterfaces().Contains(typeof(IEndpointAttribute)) && + !x.AttributeType.GetInterfaces().Contains(typeof(IPageAttribute))); + + // determining attributes + foreach (var customAttribute in settingPageType.CustomAttributes + .Where(x => x.AttributeType.GetInterfaces().Contains(typeof(IEndpointAttribute)))) + { + if (customAttribute.AttributeType.GetInterfaces().Contains(typeof(ISegmentAttribute))) + { + segment = settingPageType.GetCustomAttributes(customAttribute.AttributeType, false).FirstOrDefault() as ISegmentAttribute; + } + else if (customAttribute.AttributeType.IsGenericType && customAttribute.AttributeType.GetGenericTypeDefinition() == typeof(SettingGroupAttribute<>)) + { + group = customAttribute.AttributeType.GenericTypeArguments.FirstOrDefault(); + } + else if (customAttribute.AttributeType == typeof(SettingSectionAttribute)) + { + section = Enum.Parse(customAttribute.ConstructorArguments.FirstOrDefault().Value?.ToString()); + } + else if (customAttribute.AttributeType == typeof(SettingHideAttribute)) + { + hide = true; + } + else if (customAttribute.AttributeType.IsGenericType && customAttribute.AttributeType.GetGenericTypeDefinition() == typeof(WebIconAttribute<>)) + { + var type = customAttribute.AttributeType.GenericTypeArguments.FirstOrDefault(); + icon ??= Activator.CreateInstance(type) as IIcon; + } + else if (customAttribute.AttributeType == typeof(CacheAttribute)) + { + cache = true; + } + else if (customAttribute.AttributeType == typeof(IncludeSubPathsAttribute)) + { + includeSubPaths = Convert.ToBoolean(customAttribute.ConstructorArguments.FirstOrDefault().Value); + } + else if (customAttribute.AttributeType.Name == typeof(ConditionAttribute<>).Name && customAttribute.AttributeType.Namespace == typeof(ConditionAttribute<>).Namespace) + { + var condition = customAttribute.AttributeType.GenericTypeArguments.FirstOrDefault(); + conditions.Add(Activator.CreateInstance(condition) as ICondition); + } + } + + if (group == default) + { + _httpServerContext?.Log.Warning + ( + I18N.Translate + ( + "webexpress.webcore:settingpagemanager.register.nogroup", + id + ) + ); + } + + foreach (var customAttribute in settingPageType.CustomAttributes + .Where(x => x.AttributeType.GetInterfaces().Contains(typeof(ISettingPageAttribute)))) + { + if (customAttribute.AttributeType == typeof(TitleAttribute)) + { + title = customAttribute.ConstructorArguments.FirstOrDefault().Value?.ToString(); + } + else if (customAttribute.AttributeType.Name == typeof(ScopeAttribute<>).Name && customAttribute.AttributeType.Namespace == typeof(ScopeAttribute<>).Namespace) + { + scopes.Add(customAttribute.AttributeType.GenericTypeArguments.FirstOrDefault()); + } + } + + if (settingPageType.GetInterfaces().Where(x => x == typeof(IScope)).Any()) + { + scopes.Add(settingPageType); + } + + // assign the setting page to existing applications + foreach (var applicationContext in applicationContexts) + { + var prefix = applicationContext.ContextPath.Concat + ( + applicationContext.PluginContext != pluginContext + ? pluginContext.PluginName.ToLower() + : "" + ); + var routePath = EndpointManager.CreateEndpointRoute(settingPageType, prefix, segment); + var settingPageContext = new SettingPageContext() + { + EndpointId = new ComponentId(id), + PluginContext = pluginContext, + ApplicationContext = applicationContext, + Route = routePath, + Cache = cache, + Conditions = conditions, + IncludeSubPaths = includeSubPaths, + Attributes = attributes.Select(x => x.AttributeType), + PageTitle = title, + Scopes = scopes, + SettingGroup = _groupDictionary.GetSettingGroup(applicationContext, group), + Section = section, + Hide = hide, + Icon = icon + }; + + // create meta information of the setting page + var settingPageItem = new SettingPageItem(_componentHub.EndpointManager) + { + EndpointId = new ComponentId(id), + PluginContext = pluginContext, + ApplicationContext = applicationContext, + SettingPageContext = settingPageContext, + SettingPageClass = settingPageType, + SettingGroupType = group?.GetType(), + IncludeSubPaths = includeSubPaths, + Attributes = attributes.Select(x => x.AttributeType) + }; + + // insert the settings page into the dictionary + if (_pageDictionary.AddSettingPageItem(settingPageItem)) + { + OnAddSettingPage(settingPageItem.SettingPageContext); + + _httpServerContext?.Log.Debug + ( + I18N.Translate + ( + "webexpress.webcore:settingpagemanager.register.page", + id, + applicationContext.ApplicationId + ) + ); + } + } + } + } + + /// + /// Removes all elemets associated with the specified plugin context. + /// + /// The context of the plugin that contains the elemets to remove. + public void Remove(IPluginContext pluginContext) + { + _pageDictionary.Remove(pluginContext); + } + + /// + /// Removes all setting pages associated with the specified application context. + /// + /// The context of the application that contains the fragments to remove. + internal void Remove(IApplicationContext applicationContext) + { + foreach (var settingPageContext in _pageDictionary.Remove(applicationContext)) + { + OnRemoveSettingPage(settingPageContext); + } + } + + /// + /// Returns an enumeration of setting page contextes. + /// + /// The setting page type. + /// An enumeration of setting page contextes. + public IEnumerable GetSettingPages(Type settingPageType) + { + return _pageDictionary.GetSettingPages(settingPageType); + } + + /// + /// Returns an enumeration of setting page contexts. + /// + /// The type of the setting page. + /// The application context in which the setting pages are retrieved. + /// An enumeration of setting page contexts. + public IEnumerable GetSettingPages(Type settingPageType, IApplicationContext applicationContext) + { + return _pageDictionary.GetSettingPages(settingPageType, applicationContext); + } + + /// + /// Returns the category contexts associated with the given application context. + /// + /// The application context for which the categories are to be retrieved. + /// An enumeration of category contexts associated with the specified application context. + public IEnumerable GetSettingCategories(IApplicationContext applicationContext) + { + return _categoryDictionary.GetSettingCategories(applicationContext); + } + + /// + /// Returns the setting groups associated with the specified application context and category. + /// + /// The application context used to identify the relevant groups. + /// The category context for filtering the setting groups. + /// An enumeration of setting group contexts associated with the provided application context and category. + public IEnumerable GetSettingGroups(IApplicationContext applicationContext, ISettingCategoryContext categoryContext) + { + return _groupDictionary.GetSettingGroups(applicationContext, categoryContext); + } + + /// + /// Returns an enumeration of setting page contexts for the specified application context and category. + /// + /// The context of the application. + /// The category for which to retrieve setting pages. + /// An enumeration of setting page contexts. + public IEnumerable GetSettingPages(IApplicationContext applicationContext, ISettingCategoryContext categoryContext) + { + return _pageDictionary.GetSettingPages(applicationContext, categoryContext); + } + + /// + /// Returns an enumeration of setting page contexts for the specified application context, category, and group. + /// + /// The context of the application. + /// The group for which to retrieve setting pages. + /// An enumeration of setting page contexts. + public IEnumerable GetSettingPages(IApplicationContext applicationContext, ISettingGroupContext groupContext) + { + return _pageDictionary.GetSettingPages(applicationContext, groupContext); + } + + /// + /// Returns the first setting page context for the specified application context and category. + /// + /// The context of the application. + /// The category for which to retrieve setting pages. + /// The first setting page context or null. + public ISettingPageContext GetFirstSettingPage(IApplicationContext applicationContext, ISettingCategoryContext categoryContext) + { + var pages = _pageDictionary.GetSettingPages(applicationContext, categoryContext); + var preferences = pages.Where(x => x.Section == SettingSection.Preferences); + var primary = pages.Where(x => x.Section == SettingSection.Primary); + var secondary = pages.Where(x => x.Section == SettingSection.Secondary); + + if (preferences.Any()) + { + return preferences.OrderBy(x => x.PageTitle).FirstOrDefault(); + } + + if (primary.Any()) + { + return primary.OrderBy(x => x.PageTitle).FirstOrDefault(); + } + + if (secondary.Any()) + { + return secondary.OrderBy(x => x.PageTitle).FirstOrDefault(); + } + + return null; + } + + /// + /// Raises the AddSettingPage event. + /// + /// The setting page context. + private void OnAddSettingPage(ISettingPageContext settingPageContext) + { + AddSettingPage?.Invoke(this, settingPageContext); + } + + /// + /// Raises the RemoveSettingPage event. + /// + /// The setting page context. + private void OnRemoveSettingPage(ISettingPageContext settingPageContext) + { + RemoveSettingPage?.Invoke(this, settingPageContext); + } + + /// + /// Raises the AddSettingCategory event. + /// + /// The setting category context. + private void OnAddSettingCategory(ISettingCategoryContext settingCategoryeContext) + { + AddSettingCategory?.Invoke(this, settingCategoryeContext); + } + + /// + /// Raises the RemoveSettingCategory event. + /// + /// The setting category context. + private void OnRemoveSettingCategory(ISettingCategoryContext settingCategoryContext) + { + RemoveSettingCategory?.Invoke(this, settingCategoryContext); + } + + /// + /// Raises the AddSettingGroup event. + /// + /// The setting group context. + private void OnAddSettingGroup(ISettingGroupContext settingGroupContext) + { + AddSettingGroup?.Invoke(this, settingGroupContext); + } + + /// + /// Raises the RemoveSettingGroup event. + /// + /// The setting group context. + private void OnRemoveSettingGroup(ISettingGroupContext settingGroupContext) + { + RemoveSettingGroup?.Invoke(this, settingGroupContext); + } + + /// + /// Raises the event when an plugin is added. + /// + /// The source of the event. + /// The context of the plugin being added. + private void OnAddPlugin(object sender, IPluginContext e) + { + Register(e); + } + + /// + /// Raises the event when a plugin is removed. + /// + /// The source of the event. + /// The context of the plugin being removed. + private void OnRemovePlugin(object sender, IPluginContext e) + { + Remove(e); + } + + /// + /// Raises the event when an application is added. + /// + /// The source of the event. + /// The context of the application being added. + private void OnAddApplication(object sender, IApplicationContext e) + { + Register(e); + } + + /// + /// Raises the event when an application is removed. + /// + /// The source of the event. + /// The context of the application being removed. + private void OnRemoveApplication(object sender, IApplicationContext e) + { + Remove(e); + } + + /// + /// Release of unmanaged resources reserved during use. + /// + public void Dispose() + { + _componentHub.PluginManager.AddPlugin -= OnAddPlugin; + _componentHub.PluginManager.RemovePlugin -= OnRemovePlugin; + _componentHub.ApplicationManager.AddApplication -= OnAddApplication; + _componentHub.ApplicationManager.RemoveApplication -= OnRemoveApplication; + + GC.SuppressFinalize(this); + } + } +} \ No newline at end of file diff --git a/src/WebExpress.WebCore/WebSettingPage/SettingPageSearchResult.cs b/src/WebExpress.WebCore/WebSettingPage/SettingPageSearchResult.cs new file mode 100644 index 0000000..0fb9b07 --- /dev/null +++ b/src/WebExpress.WebCore/WebSettingPage/SettingPageSearchResult.cs @@ -0,0 +1,30 @@ +using WebExpress.WebCore.WebSettingPage.Model; + +namespace WebExpress.WebCore.WebSettingPage +{ + /// + /// Represents the result of a search on the settings page. + /// + public class SettingPageSearchResult + { + /// + /// Returns the setting context. + /// + public string Context { get; internal set; } + + /// + /// Returns the section. + /// + public SettingSection Section { get; internal set; } + + /// + /// Returns the group. + /// + public string Group { get; internal set; } + + /// + /// A list of all currently existing setting contexts that can be accessed through the settings page. + /// + public SettingPageItem Item { get; internal set; } + } +} diff --git a/src/WebExpress.WebCore/WebSettingPage/SettingSection.cs b/src/WebExpress.WebCore/WebSettingPage/SettingSection.cs new file mode 100644 index 0000000..a187bc4 --- /dev/null +++ b/src/WebExpress.WebCore/WebSettingPage/SettingSection.cs @@ -0,0 +1,23 @@ +namespace WebExpress.WebCore.WebSettingPage +{ + /// + /// Definition of keys for the identification of sections in Wen pages, which can be occupied by components. + /// + public enum SettingSection + { + /// + /// Returns the preference section. + /// + Preferences, + + /// + /// Returns the primary section. + /// + Primary, + + /// + /// Returns the secondary section. + /// + Secondary + } +} diff --git a/src/WebExpress.WebCore/WebSitemap/ISitemapManager.cs b/src/WebExpress.WebCore/WebSitemap/ISitemapManager.cs new file mode 100644 index 0000000..f702b3d --- /dev/null +++ b/src/WebExpress.WebCore/WebSitemap/ISitemapManager.cs @@ -0,0 +1,69 @@ +using System; +using System.Collections.Generic; +using WebExpress.WebCore.WebApplication; +using WebExpress.WebCore.WebComponent; +using WebExpress.WebCore.WebEndpoint; +using WebExpress.WebCore.WebMessage; +using WebExpress.WebCore.WebUri; + +namespace WebExpress.WebCore.WebSitemap +{ + /// + /// The interface of the sitemap manager. + /// + public interface ISitemapManager : IComponentManager + { + /// + /// Returns the side map. + /// + IEnumerable SiteMap { get; } + + /// + /// Rebuilds the sitemap. + /// + void Refresh(); + + /// + /// Locates the resource associated with the Uri. + /// + /// The Uri. + /// The search context. + /// The search result with the found resource or null + SearchResult SearchResource(Uri requestUri, SearchContext searchContext); + + /// + /// Returns the URI for this type based on the sitemap configuration, taking into account the specific context in which the URI is valid. + /// + /// The class from which the URI is to be determined. URI route must not have any dynamic components (such as '/a/guid/b'). + /// The application context. + /// The parameters to be considered for the uri. + /// Returns the URI taking into account the context, or null if no valid URI is found. + IUri GetUri(IApplicationContext applicationContext, params Parameter[] parameters) + where TEndpoint : IEndpoint; + + /// + /// Returns the URI for this type based on the sitemap configuration, taking into account the specific context in which the URI is valid. + /// + /// The endpoint type. + /// The application context. + /// The parameters to be considered for the uri. + /// Returns the URI taking into account the context, or null if no valid URI is found. + IUri GetUri(Type endpointType, IApplicationContext applicationContext, params Parameter[] parameters); + + /// + /// Returns the URI for this type based on the sitemap configuration, taking into account the specific context in which the URI is valid. + /// + /// The class from which the URI is to be determined. URI route must not have any dynamic components (such as '/a/guid/b'). + /// The endpoint context. + /// Returns the URI taking into account the context, or null if no valid URI is found. + IUri GetUri(IEndpointContext endpointContext) + where TEndpoint : IEndpoint; + + /// + /// Retrieves the endpoint context associated with the given URI. + /// + /// The URI resource to search for. + /// The endpoint context if found, otherwise null. + IEndpointContext GetEndpoint(UriEndpoint uri); + } +} diff --git a/src/WebExpress.WebCore/WebSitemap/SitemapNode.cs b/src/WebExpress.WebCore/WebSitemap/Model/SitemapNode.cs similarity index 72% rename from src/WebExpress.WebCore/WebSitemap/SitemapNode.cs rename to src/WebExpress.WebCore/WebSitemap/Model/SitemapNode.cs index 34ab389..6fa8516 100644 --- a/src/WebExpress.WebCore/WebSitemap/SitemapNode.cs +++ b/src/WebExpress.WebCore/WebSitemap/Model/SitemapNode.cs @@ -1,11 +1,9 @@ using System.Collections.Generic; using System.Linq; -using WebExpress.WebCore.WebApplication; -using WebExpress.WebCore.WebModule; -using WebExpress.WebCore.WebResource; +using WebExpress.WebCore.WebEndpoint; using WebExpress.WebCore.WebUri; -namespace WebExpress.WebCore.WebSitemap +namespace WebExpress.WebCore.WebSitemap.Model { /// /// A Sitemap node. @@ -18,34 +16,14 @@ public class SitemapNode public IUriPathSegment PathSegment { get; internal set; } /// - /// Returns the resource item. + /// Returns the context of the endpoint. /// - public ResourceItem ResourceItem { get; internal set; } - - /// - /// Returns the context of the application. - /// - public IApplicationContext ApplicationContext { get; internal set; } - - /// - /// Returns the context of the module. - /// - public IModuleContext ModuleContext { get; internal set; } - - /// - /// Returns the context of the resource. - /// - public IResourceContext ResourceContext { get; internal set; } - - /// - /// Returns the instance - /// - public IResource Instance { get; internal set; } + public IEndpointContext EndpointContext { get; internal set; } /// /// Returns the child nodes. /// - public ICollection Children { get; private set; } = new List(); + public ICollection Children { get; private set; } = []; /// /// Returns the parent node. @@ -114,7 +92,7 @@ public ICollection Path } /// - /// Constructor + /// Initializes a new instance of the class. /// public SitemapNode() { @@ -149,11 +127,7 @@ public SitemapNode Copy() var node = new SitemapNode() { PathSegment = PathSegment, - ResourceItem = ResourceItem, - ApplicationContext = ApplicationContext, - ModuleContext = ModuleContext, - ResourceContext = ResourceContext, - Instance = Instance, + EndpointContext = EndpointContext, Parent = Parent, Children = Children.Select(x => x.Copy()).ToList() }; @@ -164,13 +138,13 @@ public SitemapNode Copy() /// /// Convert to string. /// - /// The tree node in its string representation. + /// The sitemap node in its string representation. public override string ToString() { return Path.FirstOrDefault()?.PathSegment + string.Join ( "/", - Path.Where(x => !(x.PathSegment is UriPathSegmentRoot)) + Path.Where(x => x.PathSegment is not UriPathSegmentRoot) .Select(x => x.PathSegment?.ToString()) ); } diff --git a/src/WebExpress.WebCore/WebSitemap/SearchResult.cs b/src/WebExpress.WebCore/WebSitemap/SearchResult.cs index d57817e..f1a271b 100644 --- a/src/WebExpress.WebCore/WebSitemap/SearchResult.cs +++ b/src/WebExpress.WebCore/WebSitemap/SearchResult.cs @@ -1,7 +1,6 @@ using System.Collections.Generic; -using WebExpress.WebCore.WebApplication; -using WebExpress.WebCore.WebModule; -using WebExpress.WebCore.WebResource; +using WebExpress.WebCore.WebEndpoint; +using WebExpress.WebCore.WebSitemap.Model; using WebExpress.WebCore.WebUri; namespace WebExpress.WebCore.WebSitemap @@ -12,40 +11,15 @@ namespace WebExpress.WebCore.WebSitemap public class SearchResult { /// - /// Returns the resource id. + /// Returns the context of the endpoint. /// - public string Id { get; internal set; } - - /// - /// Returns the resource title. - /// - public string Title { get; internal set; } - - /// - /// Returns the instance. - /// - public IResource Instance { get; internal set; } + public IEndpointContext EndpointContext { get; internal set; } /// /// Returns the search context. /// public SearchContext SearchContext { get; internal set; } - /// - /// Returns the context of the application. - /// - public IApplicationContext ApplicationContext { get; internal set; } - - /// - /// Returns the context of the module. - /// - public IModuleContext ModuleContext { get; internal set; } - - /// - /// Returns the context of the resource. - /// - public IResourceContext ResourceContext { get; internal set; } - /// /// Returns the context where the resource exists. /// @@ -61,14 +35,6 @@ public class SearchResult /// Returns the uri. /// /// The uri. - public UriResource Uri { get; internal set; } - - /// - /// Constructor - /// - internal SearchResult() - { - - } + public UriEndpoint Uri { get; internal set; } } } diff --git a/src/WebExpress.WebCore/WebSitemap/SitemapManager.cs b/src/WebExpress.WebCore/WebSitemap/SitemapManager.cs index 6771810..7a5bc6c 100644 --- a/src/WebExpress.WebCore/WebSitemap/SitemapManager.cs +++ b/src/WebExpress.WebCore/WebSitemap/SitemapManager.cs @@ -1,52 +1,51 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using WebExpress.WebCore.Internationalization; using WebExpress.WebCore.WebApplication; using WebExpress.WebCore.WebComponent; +using WebExpress.WebCore.WebEndpoint; +using WebExpress.WebCore.WebLog; using WebExpress.WebCore.WebMessage; -using WebExpress.WebCore.WebModule; -using WebExpress.WebCore.WebPage; -using WebExpress.WebCore.WebPlugin; -using WebExpress.WebCore.WebResource; +using WebExpress.WebCore.WebSitemap.Model; using WebExpress.WebCore.WebUri; namespace WebExpress.WebCore.WebSitemap { /// - /// The resource manager manages WebExpress elements, which can be called with a URI (Uniform Resource Identifier). + /// The sitemap manager manages WebExpress elements, which can be called with a URI (Uniform Resource Identifier). /// - public sealed class SitemapManager : IComponent, ISystemComponent + public sealed class SitemapManager : ISitemapManager, ISystemComponent { - /// - /// Returns the reference to the context of the host. - /// - public IHttpServerContext HttpServerContext { get; private set; } + private SitemapNode _root = new(); + private readonly IComponentHub _componentHub; + private readonly IHttpServerContext _httpServerContext; + private readonly IUri _serverUri; /// /// Returns the side map. /// - private SitemapNode SiteMap { get; set; } = new SitemapNode(); + public IEnumerable SiteMap => _root.GetPreOrder() + .Where(x => x != null) + .Select(x => x.EndpointContext); /// - /// Constructor + /// Initializes a new instance of the class. /// - internal SitemapManager() + /// The component hub. + /// The reference to the context of the host. + [SuppressMessage("CodeQuality", "IDE0051:Remove unused private members", Justification = "Used via Reflection.")] + private SitemapManager(IComponentHub componentHub, IHttpServerContext httpServerContext) { + _componentHub = componentHub; + _httpServerContext = httpServerContext; + _serverUri = new UriEndpoint(_httpServerContext.Endpoints.FirstOrDefault(e => e.Uri.StartsWith("https"))?.ToString() + ?? _httpServerContext.Endpoints.FirstOrDefault()?.ToString() ?? ""); - } - - /// - /// Initialization - /// - /// The reference to the context of the host. - public void Initialization(IHttpServerContext context) - { - HttpServerContext = context; - - HttpServerContext.Log.Debug + _httpServerContext.Log.Debug ( - InternationalizationManager.I18N("webexpress:sitemapmanager.initialization") + I18N.Translate("webexpress.webcore:sitemapmanager.initialization") ); } @@ -57,13 +56,13 @@ public void Refresh() { var newSiteMapNode = new SitemapNode() { PathSegment = new UriPathSegmentRoot() }; - HttpServerContext.Log.Debug + _httpServerContext.Log.Debug ( - InternationalizationManager.I18N("webexpress:sitemapmanager.refresh") + I18N.Translate("webexpress.webcore:sitemapmanager.refresh") ); // applications - var applications = ComponentManager.ApplicationManager.Applications + var applications = _componentHub.ApplicationManager.Applications .Select(x => new { ApplicationContext = x, @@ -80,53 +79,28 @@ public void Refresh() )); } - // modules - var modules = ComponentManager.ModuleManager.Modules + // endpoints + var resources = _componentHub.EndpointManager.Endpoints + .Where(x => x.Route != null) .Select(x => new { - ModuleContext = x, - x.ContextPath.PathSegments + EndpointContext = x, + x.Route.PathSegments }) .OrderBy(x => x.PathSegments.Count()); - foreach (var module in modules) - { - MergeSitemap(newSiteMapNode, CreateSiteMap - ( - new Queue(module.PathSegments), - module.ModuleContext - )); - } - - // resourcen - var resources = ComponentManager.ResourceManager.ResourceItems - .SelectMany(x => x.ResourceContexts - .Select(y => new - { - Item = x, - ResourceContext = y, - y.Uri.PathSegments - })) - .OrderBy(x => x.PathSegments.Count()); - foreach (var item in resources) { MergeSitemap(newSiteMapNode, CreateSiteMap ( new Queue(item.PathSegments), - item.Item, - item.ResourceContext + item.EndpointContext )); } - SiteMap = newSiteMapNode; + _root = newSiteMapNode; - using (var frame = new LogFrameSimple(HttpServerContext.Log)) - { - var list = new List(); - PrepareForLog(null, list, 2); - HttpServerContext.Log.Info(string.Join(Environment.NewLine, list)); - } + Log(); } /// @@ -140,15 +114,15 @@ public SearchResult SearchResource(Uri requestUri, SearchContext searchContext) var variables = new Dictionary(); var result = SearchNode ( - SiteMap, - new Queue(requestUri.Segments.Select(x => (x == "/" ? x : (x.EndsWith("/") ? x[..^1] : x)))), + _root, + new Queue(requestUri.Segments.Select(x => x == "/" ? x : (x.EndsWith('/') ? x[..^1] : x))), new Queue(), searchContext ); - if (result != null && result.ResourceContext != null) + if (result != null && result.EndpointContext != null) { - if (!result.ResourceContext.Conditions.Any() || result.ResourceContext.Conditions.All(x => x.Fulfillment(searchContext.HttpContext?.Request))) + if (!result.EndpointContext.Conditions.Any() || result.EndpointContext.Conditions.All(x => x.Fulfillment(searchContext.HttpContext?.Request))) { return result; } @@ -159,50 +133,102 @@ public SearchResult SearchResource(Uri requestUri, SearchContext searchContext) } /// - /// Determines the Uri from the sitemap of a class, taking into account the context in which the uri is valid. + /// Returns the URI for this type based on the sitemap configuration, taking into account the specific context + /// in which the URI is valid. /// - /// The class from which the uri is to be determined. The class uri must not have any dynamic components (such as '/a//b'). - /// - /// Returns the uri taking into account the context or null. - public UriResource GetUri(params Parameter[] parameters) where T : IResource + /// The class from which the URI is to be determined. URI route must not have any dynamic components (such as '/a/guid/b'). + /// The application context. + /// The parameters to be considered for the uri. + /// Returns the URI taking into account the context, or null if no valid URI is found. + public IUri GetUri(IApplicationContext applicationContext, params Parameter[] parameters) + where TEndpoint : IEndpoint { - var node = SiteMap.GetPreOrder() - .Where(x => x.ResourceItem?.ResourceClass == typeof(T)) + return GetUri(typeof(TEndpoint), applicationContext, parameters); + } + + /// + /// Returns the URI for this type based on the sitemap configuration, taking into account the specific context in which the URI is valid. + /// + /// The endpoint type. + /// The application context. + /// The parameters to be considered for the uri. + /// Returns the URI taking into account the context, or null if no valid URI is found. + public IUri GetUri(Type endpointType, IApplicationContext applicationContext, params Parameter[] parameters) + { + var endpointContexts = _componentHub.EndpointManager.GetEndpoints(endpointType, applicationContext); + + var node = _root.GetPreOrder() + .Where(x => endpointContexts.Contains(x.EndpointContext)) .FirstOrDefault(); - return node?.ResourceContext?.Uri.SetParameters(parameters); + return new UriEndpoint(_serverUri, node?.EndpointContext?.Route.PathSegments, null).SetParameters(parameters); } /// - /// Determines the Uri from the sitemap of a class, taking into account the context in which the uri is valid. + /// Returns the URI for this type based on the sitemap configuration, taking into account the specific context in which the URI is valid. /// - /// The class from which the uri is to be determined. The class uri must not have any dynamic components (such as '/a//b'). - /// The module context. - /// Returns the uri taking into account the context or null. - public UriResource GetUri(IModuleContext moduleContext) where T : IResource + /// The class from which the URI is to be determined. URI route must not have any dynamic components (such as '/a/guid/b'). + /// The endpoint context. + /// Returns the URI taking into account the context, or null if no valid URI is found. + public IUri GetUri(IEndpointContext endpointContext) + where TEnpoint : IEndpoint { - var node = SiteMap.GetPreOrder() - .Where(x => x.ResourceItem?.ResourceClass == typeof(T)) - .Where(x => x.ModuleContext == moduleContext) + var endpointContexts = _componentHub.EndpointManager.GetEndpoints(typeof(TEnpoint), endpointContext.ApplicationContext) + .Where(x => x.EndpointId.Equals(endpointContext.EndpointId)); + + var node = _root.GetPreOrder() + .Where(x => endpointContexts.Contains(x.EndpointContext)) .FirstOrDefault(); - return node?.ResourceContext?.Uri; + return new UriEndpoint(_serverUri, node?.EndpointContext?.Route.PathSegments, null); } /// - /// Determines the Uri from the sitemap of a class, taking into account the context in which the uri is valid. + /// Retrieves the endpoint context associated with the given URI. /// - /// The class from which the uri is to be determined. The class uri must not have any dynamic components (such as '/a//b'). - /// The module context. - /// Returns the uri taking into account the context or null. - public UriResource GetUri(IResourceContext resourceContext) where T : IResource + /// The URI resource to search for. + /// The endpoint context if found, otherwise null. + public IEndpointContext GetEndpoint(UriEndpoint uri) { - var node = SiteMap.GetPreOrder() - .Where(x => x.ResourceItem?.ResourceClass == typeof(T)) - .Where(x => x.ModuleContext == resourceContext.ModuleContext) - .FirstOrDefault(); + var variables = new Dictionary(); + var result = SearchNode + ( + _root, + new Queue(uri.PathSegments.Select(x => x.ToString())), + new Queue(), + new SearchContext() + ); + return result?.EndpointContext; + } - return node?.ResourceContext?.Uri; + /// + /// Creates the sitemap. Works recursively. + /// It is important for the algorithm that the addition of application is sorted + /// by the number of path segments in ascending order. + /// + /// The path segments of the context path. + /// The application context. + /// The sitemap root node. + private static SitemapNode CreateSiteMap + ( + Queue contextPathSegments, + IApplicationContext applicationContext + ) + { + if (contextPathSegments.Peek() is UriPathSegmentRoot) + { + contextPathSegments.Dequeue(); + } + + var root = new SitemapNode() { PathSegment = new UriPathSegmentRoot() }; + var next = CreateSiteMap(contextPathSegments, applicationContext, root); + + if (next != null) + { + root.Children.Add(next); + } + + return root; } /// @@ -218,18 +244,23 @@ private static SitemapNode CreateSiteMap ( Queue contextPathSegments, IApplicationContext applicationContext, - SitemapNode parent = null + SitemapNode parent ) { - var pathSegment = contextPathSegments.Any() ? contextPathSegments.Dequeue() : null; + var pathSegment = contextPathSegments.Count != 0 ? contextPathSegments.Dequeue() : null; + + if (pathSegment == null) + { + return null; + } + var node = new SitemapNode() { - PathSegment = pathSegment as IUriPathSegment, + PathSegment = pathSegment, Parent = parent, - ApplicationContext = applicationContext }; - if (contextPathSegments.Any()) + if (contextPathSegments.Count != 0) { node.Children.Add(CreateSiteMap(contextPathSegments, applicationContext, node)); } @@ -239,69 +270,71 @@ private static SitemapNode CreateSiteMap /// /// Creates the sitemap. Works recursively. - /// It is important for the algorithm that the addition of module is sorted + /// It is important for the algorithm that the addition is sorted /// by the number of path segments in ascending order. /// /// The path segments of the context path. - /// The application context. - /// The parent node or null if root. + /// The endpoint context. /// The sitemap root node. private static SitemapNode CreateSiteMap ( Queue contextPathSegments, - IModuleContext moduleContext, - SitemapNode parent = null + IEndpointContext endpointContext ) { - var pathSegment = contextPathSegments.Any() ? contextPathSegments.Dequeue() : null; - var node = new SitemapNode() + if (contextPathSegments.Peek() is UriPathSegmentRoot) { - PathSegment = pathSegment as IUriPathSegment, - Parent = parent, - ApplicationContext = moduleContext?.ApplicationContext, - ModuleContext = moduleContext - }; + contextPathSegments.Dequeue(); + } + + var root = new SitemapNode() { PathSegment = new UriPathSegmentRoot() }; + var next = CreateSiteMap(contextPathSegments, endpointContext, root); - if (contextPathSegments.Any()) + if (next != null) { - node.Children.Add(CreateSiteMap(contextPathSegments, moduleContext, node)); + root.Children.Add(next); + } + else + { + root.EndpointContext = endpointContext; } - return node; + return root; } /// /// Creates the sitemap. Works recursively. - /// It is important for the algorithm that the addition of resources is sorted + /// It is important for the algorithm that the addition of endpoint is sorted /// by the number of path segments in ascending order. /// /// The path segments of the context path. - /// The resource item. - /// The resource context. + /// The endpoint context. /// The parent node or null if root. /// The sitemap parent node. private static SitemapNode CreateSiteMap ( Queue contextPathSegments, - ResourceItem resourceItem, - IResourceContext resourceContext, + IEndpointContext endpointContext, SitemapNode parent = null ) { - var pathSegment = contextPathSegments.Any() ? contextPathSegments.Dequeue() : null; + var pathSegment = contextPathSegments.Count != 0 ? contextPathSegments.Dequeue() : null; + + if (pathSegment == null) + { + return null; + } + var node = new SitemapNode() { - PathSegment = pathSegment as IUriPathSegment, + PathSegment = pathSegment, Parent = parent, - ResourceItem = !contextPathSegments.Any() ? resourceItem : null, - ApplicationContext = resourceContext?.ModuleContext?.ApplicationContext, - ModuleContext = resourceContext?.ModuleContext, - ResourceContext = resourceContext + EndpointContext = endpointContext }; - if (contextPathSegments.Any()) + if (contextPathSegments.Count != 0) { - node.Children.Add(CreateSiteMap(contextPathSegments, resourceItem, resourceContext, node)); + node.Children.Add(CreateSiteMap(contextPathSegments, endpointContext, node)); } return node; @@ -312,7 +345,7 @@ private static SitemapNode CreateSiteMap /// /// The first sitemap to be merged. /// The second sitemap to be merged. - private void MergeSitemap(SitemapNode first, SitemapNode second) + private static void MergeSitemap(SitemapNode first, SitemapNode second) { if (first.PathSegment.Equals(second.PathSegment)) { @@ -320,15 +353,7 @@ private void MergeSitemap(SitemapNode first, SitemapNode second) { foreach (var fc in first.Children.Where(x => x.PathSegment.Equals(sc.PathSegment))) { - if (fc.ResourceItem == null) - { - fc.ResourceItem = sc.ResourceItem; - fc.ApplicationContext = sc.ApplicationContext; - fc.ModuleContext = sc.ModuleContext; - fc.ResourceContext = sc.ResourceContext; - fc.Instance = sc.Instance; - fc.Parent = sc.Parent; - } + fc.EndpointContext ??= sc.EndpointContext; MergeSitemap(fc, sc); return; @@ -357,8 +382,8 @@ private SearchResult SearchNode SearchContext searchContext ) { - var pathSegment = inPathSegments.Any() ? inPathSegments.Dequeue() : null; - var nextPathSegment = inPathSegments.Any() ? inPathSegments.Peek() : null; + var pathSegment = inPathSegments.Count != 0 ? inPathSegments.Dequeue() : null; + var nextPathSegment = inPathSegments.Count != 0 ? inPathSegments.Peek() : null; if (IsMatched(node, pathSegment)) { @@ -368,34 +393,50 @@ SearchContext searchContext variable.Value = pathSegment; } + var type = node.EndpointContext?.GetType(); + outPathSegments.Enqueue(copy); - if (nextPathSegment == null && node.ResourceItem != null) + if (nextPathSegment == null) { return new SearchResult() { - Id = node.ResourceItem.ResourceId, - Title = node.ResourceItem.Title, - ApplicationContext = node.ApplicationContext, - ModuleContext = node.ModuleContext, - ResourceContext = node.ResourceContext, + EndpointContext = node.EndpointContext, SearchContext = searchContext, - Uri = new UriResource(outPathSegments.ToArray()), - Instance = CreateInstance(node, new UriResource(outPathSegments.ToArray()), searchContext), + Uri = new UriEndpoint + ( + [.. + outPathSegments.Concat(inPathSegments + .Select(x => new UriPathSegmentConstant(x))) + ] + ) + { + BasePath = new UriEndpoint([.. outPathSegments]) + } }; } - else if (node.IsLeaf && nextPathSegment != null && node.ResourceItem != null && node.ResourceItem.IncludeSubPaths) + else if + ( + node.IsLeaf + && nextPathSegment != null + && node.EndpointContext != null + && node.EndpointContext.IncludeSubPaths + ) { return new SearchResult() { - Id = node.ResourceItem.ResourceId, - Title = node.ResourceItem.Title, - ApplicationContext = node.ApplicationContext, - ModuleContext = node.ModuleContext, - ResourceContext = node.ResourceContext, + EndpointContext = node.EndpointContext, SearchContext = searchContext, - Uri = new UriResource(outPathSegments.ToArray()), - Instance = CreateInstance(node, new UriResource(outPathSegments.ToArray()), searchContext), + Uri = new UriEndpoint + ( + [.. + outPathSegments.Concat(inPathSegments + .Select(x => new UriPathSegmentConstant(x))) + ] + ) + { + BasePath = new UriEndpoint([.. outPathSegments]) + } }; } @@ -406,62 +447,7 @@ SearchContext searchContext } // 404 - return new SearchResult() - { - ApplicationContext = node.ApplicationContext, - ModuleContext = node.ModuleContext, - ResourceContext = node.ResourceContext, - SearchContext = searchContext, - Uri = new UriResource(outPathSegments.ToArray()) - }; - } - - /// - /// Creates a new instance or if caching is active, a possibly existing instance is returned. - /// - /// The sitemap node. - /// The uri. - /// The search context. - /// The instance or null. - private static IResource CreateInstance(SitemapNode node, UriResource uri, SearchContext context) - { - if (node == null || node.ResourceItem == null || node.ResourceContext == null) - { - return null; - } - - if (node.ResourceContext.Cache && node.Instance != null) - { - return node.Instance; - } - - var instance = Activator.CreateInstance(node.ResourceItem.ResourceClass) as IResource; - - if (instance is II18N i18n) - { - i18n.Culture = context.Culture; - } - - if (instance is Resource resorce) - { - resorce.Id = node.ResourceItem?.ResourceId; - resorce.ApplicationContext = node.ResourceContext?.ModuleContext?.ApplicationContext; - resorce.ModuleContext = node.ResourceContext?.ModuleContext; - } - - if (instance is IPage page) - { - page.Title = node.ResourceItem?.Title; - } - - instance.Initialization(node.ResourceContext); - - if (node.ResourceContext.Cache) - { - node.Instance = instance; - } - - return instance; + return null; } /// @@ -477,38 +463,59 @@ private static bool IsMatched(SitemapNode node, string pathSegement) return false; } - return node.PathSegment.IsMatched(pathSegement); + return node.PathSegment?.IsMatched(pathSegement) ?? false; } /// /// Information about the component is collected and prepared for output in the log. /// - /// The context of the plugin. - /// A list of log entries. - /// The shaft deep. - public void PrepareForLog(IPluginContext pluginContext, IList output, int deep) + private void Log() { - output.Add - ( - InternationalizationManager.I18N + if (!SiteMap.Any()) + { + return; + } + + using var frame = new LogFrameSimple(_httpServerContext.Log); + var list = new List + { + I18N.Translate ( - "webexpress:sitemapmanager.sitemap" + "webexpress.webcore:sitemapmanager.titel" ) - ); + }; - var preorder = SiteMap + var preorder = _root .GetPreOrder() - .Select(x => InternationalizationManager.I18N + .Select(x => I18N.Translate ( - "webexpress:sitemapmanager.preorder", + "webexpress.webcore:sitemapmanager.preorder", " " + x.ToString().PadRight(60), - x.ResourceItem?.ResourceId ?? "" + x.EndpointContext?.EndpointId.ToString() ?? "" )); foreach (var node in preorder) { - output.Add(node); + list.Add(node); } + + _httpServerContext.Log.Info(string.Join(Environment.NewLine, list)); + } + + /// + /// Returns a string that represents the current sitemap. + /// + /// A string that represents the current sitemap. + public override string ToString() + { + return string.Join(" | ", _root.GetPreOrder()); + } + + /// + /// Release of unmanaged resources reserved during use. + /// + public void Dispose() + { } } } diff --git a/src/WebExpress.WebCore/WebStatusPage/IStatusPage.cs b/src/WebExpress.WebCore/WebStatusPage/IStatusPage.cs index 5b5b7ab..9b6158b 100644 --- a/src/WebExpress.WebCore/WebStatusPage/IStatusPage.cs +++ b/src/WebExpress.WebCore/WebStatusPage/IStatusPage.cs @@ -1,62 +1,27 @@ -using WebExpress.WebCore.WebApplication; -using WebExpress.WebCore.WebMessage; -using WebExpress.WebCore.WebModule; -using WebExpress.WebCore.WebResource; -using WebExpress.WebCore.WebUri; +using WebExpress.WebCore.WebComponent; +using WebExpress.WebCore.WebPage; namespace WebExpress.WebCore.WebStatusPage { /// /// Interface of the status pages. /// - public interface IStatusPage + public interface IStatusPage : IStatusPage { - /// - /// Returns the resource Id. - /// - string Id { get; } - - /// - /// Returns the context of the application. - /// - IApplicationContext ApplicationContext { get; } - - /// - /// Returns the context of the module. - /// - IModuleContext ModuleContext { get; } - - /// - /// Returns or sets the status code. - /// - int StatusCode { get; set; } - - /// - /// Returns or sets the status title. - /// - string StatusTitle { get; set; } - - /// - /// Returns or sets the status message. - /// - string StatusMessage { get; set; } - - /// - /// Returns or sets the status icon. - /// - UriResource StatusIcon { get; set; } - /// - /// Initialization - /// - /// The context of the resource. - void Initialization(IResourceContext resourceContext); + } + /// + /// Defines the contract for a status page resource that can be rendered using a specific context. + /// + /// The type of the render context. + public interface IStatusPage : IComponent where T : IVisualTree + { /// - /// Processing of the resource. + /// Processing of the status page. /// - /// The request. - /// The response. - Response Process(Request request); + /// The context for rendering the status page. + /// The visual tree to be rendered. + void Process(IRenderContext context, T visualTree); } } diff --git a/src/WebExpress.WebCore/WebStatusPage/IStatusPageContext.cs b/src/WebExpress.WebCore/WebStatusPage/IStatusPageContext.cs new file mode 100644 index 0000000..55f24df --- /dev/null +++ b/src/WebExpress.WebCore/WebStatusPage/IStatusPageContext.cs @@ -0,0 +1,43 @@ +using WebExpress.WebCore.WebApplication; +using WebExpress.WebCore.WebComponent; +using WebExpress.WebCore.WebEndpoint; +using WebExpress.WebCore.WebPlugin; + +namespace WebExpress.WebCore.WebStatusPage +{ + /// + /// Represents the context for a status page. + /// + public interface IStatusPageContext : IContext + { + /// + /// Returns the associated plugin context. + /// + IPluginContext PluginContext { get; } + + /// + /// Returns the associated application context. + /// + IApplicationContext ApplicationContext { get; } + + /// + /// Returns the status page id. + /// + IComponentId StatusPageId { get; } + + /// + /// Returns the status code. + /// + int StatusCode { get; } + + /// + /// Returns the status title. + /// + string StatusTitle { get; } + + /// + /// Returns the status icon. + /// + IRoute StatusIcon { get; } + } +} diff --git a/src/WebExpress.WebCore/WebStatusPage/IStatusPageManager.cs b/src/WebExpress.WebCore/WebStatusPage/IStatusPageManager.cs new file mode 100644 index 0000000..d1c9b05 --- /dev/null +++ b/src/WebExpress.WebCore/WebStatusPage/IStatusPageManager.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using WebExpress.WebCore.WebApplication; +using WebExpress.WebCore.WebComponent; +using WebExpress.WebCore.WebMessage; + +namespace WebExpress.WebCore.WebStatusPage +{ + /// + /// Management of status pages. + /// + public interface IStatusPageManager : IComponentManager + { + /// + /// An event that fires when an status page is added. + /// + event EventHandler AddStatusPage; + + /// + /// An event that fires when an status page is removed. + /// + event EventHandler RemoveStatusPage; + + /// + /// Returns all status pages. + /// + IEnumerable StatusPages { get; } + + /// + /// Determines the status page for a given application context and status type. + /// + /// The context of the application. + /// The status page class. + /// The context of the status page or null. + IStatusPageContext GetStatusPage(IApplicationContext applicationContext, Type statusPageClass); + + /// + /// Creates a status response. + /// + /// The status message. + /// The status code. + /// The application context where the status pages are located or null for an undefined page (may be from another application) that matches the status code. + /// The request. + /// The response or null. + Response CreateStatusResponse(string message, int status, IApplicationContext applicationContext, Request request); + } +} diff --git a/src/WebExpress.WebCore/WebStatusPage/Model/StatusPageDictionary.cs b/src/WebExpress.WebCore/WebStatusPage/Model/StatusPageDictionary.cs new file mode 100644 index 0000000..7ed442d --- /dev/null +++ b/src/WebExpress.WebCore/WebStatusPage/Model/StatusPageDictionary.cs @@ -0,0 +1,137 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using WebExpress.WebCore.WebApplication; +using WebExpress.WebCore.WebPlugin; + +namespace WebExpress.WebCore.WebStatusPage.Model +{ + /// + /// Represents a dictionary that provides a mapping from plugin contexts to dictionaries of application contexts and status page items. + /// + internal class StatusPageDictionary : Dictionary>> + { + /// + /// Adds a status page item to the dictionary. + /// + /// The plugin context. + /// The application context. + /// The status code. + /// The status page item. + /// True if the status page item was added successfully, false if an element with the same status code already exists. + public bool AddStatusPageItem(IPluginContext pluginContext, IApplicationContext applicationContext, int statusCode, StatusPageItem statusPageItem) + { + if (!ContainsKey(pluginContext)) + { + this[pluginContext] = []; + } + + var appContextDict = this[pluginContext]; + + if (!appContextDict.ContainsKey(applicationContext)) + { + appContextDict[applicationContext] = []; + } + + var statusCodeDict = appContextDict[applicationContext]; + + if (statusCodeDict.ContainsKey(statusCode)) + { + return false; // item with the same status code already exists + } + + statusCodeDict[statusCode] = statusPageItem; + + return true; + } + + /// + /// Removes a status page item from the dictionary. + /// + /// The plugin context. + /// The application context. + /// The status code. + public void RemoveStatusPageItem(IPluginContext pluginContext, IApplicationContext applicationContext, int statusCode) + { + if (ContainsKey(pluginContext)) + { + var appContextDict = this[pluginContext]; + + if (appContextDict.ContainsKey(applicationContext)) + { + var statusCodeDict = appContextDict[applicationContext]; + + if (statusCodeDict.ContainsKey(statusCode)) + { + statusCodeDict.Remove(statusCode); + + if (statusCodeDict.Count == 0) + { + appContextDict.Remove(applicationContext); + + if (appContextDict.Count == 0) + { + Remove(pluginContext); + } + } + } + } + } + } + + /// + /// Returns the status page item from the dictionary. + /// + /// The application context. + /// The status code. + /// The status page item if found, otherwise null. + public StatusPageItem GetStatusPageItem(IApplicationContext applicationContext, int statusCode) + { + if (applicationContext != null && ContainsKey(applicationContext?.PluginContext)) + { + var appContextDict = this[applicationContext?.PluginContext]; + + if (appContextDict.ContainsKey(applicationContext)) + { + var statusCodeDict = appContextDict[applicationContext]; + + if (statusCodeDict.ContainsKey(statusCode)) + { + return statusCodeDict[statusCode]; + } + } + } + + return null; + } + + /// + /// Returns the status page item based on the application context and status page type. + /// + /// The application context. + /// The type of the status page. + /// The status page item if found, otherwise null. + public StatusPageItem GetStatusPageItem(IApplicationContext applicationContext, Type statusPageType) + { + return Values + .SelectMany(x => x) + .Where(x => x.Key == applicationContext) + .Select(x => x.Value) + .SelectMany(x => x.Values) + .FirstOrDefault(x => x.StatusPageClass == statusPageType); + } + + /// + /// Returns all StatusPageContexts for a given plugin context. + /// + /// The plugin context. + /// An IEnumerable of status page contexts. + public IEnumerable GetStatusPageContexts(IPluginContext pluginContext) + { + return this.Where(entry => entry.Key == pluginContext) + .SelectMany(entry => entry.Value.Values) + .SelectMany(dict => dict.Values) + .Select(item => item.StatusPageContext); + } + } +} diff --git a/src/WebExpress.WebCore/WebStatusPage/Model/StatusPageItem.cs b/src/WebExpress.WebCore/WebStatusPage/Model/StatusPageItem.cs new file mode 100644 index 0000000..22d9f86 --- /dev/null +++ b/src/WebExpress.WebCore/WebStatusPage/Model/StatusPageItem.cs @@ -0,0 +1,53 @@ +using System; +using WebExpress.WebCore.WebApplication; +using WebExpress.WebCore.WebPlugin; + +namespace WebExpress.WebCore.WebStatusPage.Model +{ + /// + /// Represents a status page item. + /// + internal class StatusPageItem + { + /// + /// Returns the associated plugin context. + /// + public IPluginContext PluginContext { get; internal set; } + + /// + /// Returns the associated application context. + /// + public IApplicationContext ApplicationContext { get; internal set; } + + /// + /// Returns status page context. + /// + public StatusPageContext StatusPageContext { get; internal set; } + + /// + /// Returns the status code. + /// + public Type StatusResponse { get; internal set; } + + /// + /// Returns the type of status page. + /// + public Type StatusPageClass { get; internal set; } + + /// + /// Performs application-specific tasks related to sharing, returning, or resetting unmanaged resources. + /// + public void Dispose() + { + } + + /// + /// Convert the status page element to a string. + /// + /// The status page element in its string representation. + public override string ToString() + { + return $"StatusPage '{StatusPageContext?.StatusPageId}'"; + } + } +} diff --git a/src/WebExpress.WebCore/WebStatusPage/ResponseDictionary.cs b/src/WebExpress.WebCore/WebStatusPage/ResponseDictionary.cs deleted file mode 100644 index 498a6db..0000000 --- a/src/WebExpress.WebCore/WebStatusPage/ResponseDictionary.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System.Collections.Generic; -using WebExpress.WebCore.WebPlugin; - -namespace WebExpress.WebCore.WebStatusPage -{ - /// - /// key = plugin context - /// value = ResponseDictionaryItem - /// - public class ResponseDictionary : Dictionary - { - } -} diff --git a/src/WebExpress.WebCore/WebStatusPage/ResponseDictionaryItem.cs b/src/WebExpress.WebCore/WebStatusPage/ResponseDictionaryItem.cs deleted file mode 100644 index 680b3e2..0000000 --- a/src/WebExpress.WebCore/WebStatusPage/ResponseDictionaryItem.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Collections.Generic; - -namespace WebExpress.WebCore.WebStatusPage -{ - /// - /// key = statuscode - /// value = status page item - /// - public class ResponseDictionaryItem : Dictionary - { - } -} diff --git a/src/WebExpress.WebCore/WebStatusPage/ResponseItem.cs b/src/WebExpress.WebCore/WebStatusPage/ResponseItem.cs deleted file mode 100644 index 5643d56..0000000 --- a/src/WebExpress.WebCore/WebStatusPage/ResponseItem.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System; -using WebExpress.WebCore.WebPlugin; - -namespace WebExpress.WebCore.WebStatusPage -{ - public class ResponseItem - { - /// - /// Returns the associated plugin context. - /// - public IPluginContext PluginContext { get; internal set; } - - /// - /// Returns or sets the resource id. - /// - public string Id { get; internal set; } - - /// - /// Returns or sets the status code. - /// - public int StatusCode { get; internal set; } - - /// - /// Returns or sets the type of status page. - /// - public Type StatusPageClass { get; internal set; } - - /// - /// Returns or sets the module id. - /// - public string ModuleId { get; internal set; } - } -} diff --git a/src/WebExpress.WebCore/WebStatusPage/ResponseManager.cs b/src/WebExpress.WebCore/WebStatusPage/ResponseManager.cs deleted file mode 100644 index fc76557..0000000 --- a/src/WebExpress.WebCore/WebStatusPage/ResponseManager.cs +++ /dev/null @@ -1,346 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using WebExpress.WebCore.Internationalization; -using WebExpress.WebCore.WebAttribute; -using WebExpress.WebCore.WebComponent; -using WebExpress.WebCore.WebPlugin; -using WebExpress.WebCore.WebResource; - -namespace WebExpress.WebCore.WebStatusPage -{ - /// - /// Management of status pages. - /// - public class ResponseManager : IComponentPlugin, ISystemComponent - { - /// - /// An event that fires when an status page is added. - /// - public event EventHandler AddStatusPage; - - /// - /// An event that fires when an status page is removed. - /// - public event EventHandler RemoveStatusPage; - - /// - /// Returns or sets the reference to the context of the host. - /// - public IHttpServerContext HttpServerContext { get; private set; } - - /// - /// Returns the directory where the status pages are listed. - /// - private ResponseDictionary Dictionary { get; } = new ResponseDictionary(); - - /// - /// Returns the default Items. - /// - private ResponseDictionaryItem Defaults { get; } = new ResponseDictionaryItem(); - - /// - /// Constructor - /// - internal ResponseManager() - { - ComponentManager.PluginManager.AddPlugin += (sender, pluginContext) => - { - Register(pluginContext); - }; - - ComponentManager.PluginManager.RemovePlugin += (sender, pluginContext) => - { - Remove(pluginContext); - }; - } - - /// - /// Initialization - /// - /// The reference to the context of the host. - public void Initialization(IHttpServerContext context) - { - HttpServerContext = context; - - HttpServerContext.Log.Debug - ( - InternationalizationManager.I18N("webexpress:responsemanager.initialization") - ); - } - - /// - /// Discovers and registers status pages from the specified plugin. - /// - /// A context of a plugin whose status pages are to be registered. - public void Register(IPluginContext pluginContext) - { - var assembly = pluginContext?.Assembly; - - foreach (var resource in assembly.GetTypes() - .Where(x => x.IsClass == true && x.IsSealed && x.IsPublic) - .Where(x => x.GetInterface(typeof(IStatusPage).Name) != null)) - { - var id = resource.Name?.ToLower(); - var statusCode = -1; - var moduleId = string.Empty; - var defaultItem = false; - - foreach (var customAttribute in resource.CustomAttributes - .Where(x => x.AttributeType.GetInterfaces().Contains(typeof(IApplicationAttribute)))) - { - if (customAttribute.AttributeType == typeof(StatusCodeAttribute)) - { - statusCode = Convert.ToInt32(customAttribute.ConstructorArguments.FirstOrDefault().Value?.ToString()); - } - else if (customAttribute.AttributeType.Name == typeof(ModuleAttribute<>).Name && customAttribute.AttributeType.Namespace == typeof(ModuleAttribute<>).Namespace) - { - moduleId = customAttribute.AttributeType.GenericTypeArguments.FirstOrDefault()?.FullName?.ToLower(); - } - } - - foreach (var customAttribute in resource.CustomAttributes - .Where(x => x.AttributeType.GetInterfaces().Contains(typeof(IStatusPageAttribute)))) - { - if (customAttribute.AttributeType == typeof(DefaultAttribute)) - { - defaultItem = true; - } - } - - if (statusCode > 0) - { - if (!Dictionary.ContainsKey(pluginContext)) - { - Dictionary.Add(pluginContext, new ResponseDictionaryItem()); - } - - var item = Dictionary[pluginContext]; - if (!item.ContainsKey(statusCode)) - { - item.Add(statusCode, new ResponseItem() - { - Id = id, - StatusCode = statusCode, - StatusPageClass = resource, - PluginContext = pluginContext, - ModuleId = moduleId - }); - HttpServerContext.Log.Debug - ( - InternationalizationManager.I18N - ( - "webexpress:responsemanager.register", - statusCode, - moduleId, - resource.Name - ) - ); - } - else - { - HttpServerContext.Log.Debug - ( - InternationalizationManager.I18N - ( - "webexpress:responsemanager.duplicat", - statusCode, - moduleId, - resource.Name - ) - ); - } - - // default - if (!Defaults.ContainsKey(statusCode)) - { - Defaults.Add(statusCode, new ResponseItem() - { - Id = id, - StatusCode = statusCode, - StatusPageClass = resource, - PluginContext = pluginContext, - ModuleId = moduleId - }); - } - else if (defaultItem) - { - Defaults[statusCode] = new ResponseItem() - { - Id = id, - StatusCode = statusCode, - StatusPageClass = resource, - PluginContext = pluginContext, - ModuleId = moduleId - }; - } - - } - else - { - HttpServerContext.Log.Debug - ( - InternationalizationManager.I18N - ( - "webexpress:responsemanager.statuscode", - moduleId, - resource.Name - ) - ); - } - } - } - - /// - /// Discovers and registers entries from the specified plugin. - /// - /// A list with plugin contexts that contain the status pages. - public void Register(IEnumerable pluginContexts) - { - foreach (var pluginContext in pluginContexts) - { - Register(pluginContext); - } - } - - /// - /// Returns the status codes for a given plugin. - /// - /// The context of the plugin. - /// An enumeration of the status codes for the given plugin. - internal IEnumerable GetStatusCodes(IPluginContext pluginContext) - { - if (pluginContext == null) - { - return Enumerable.Empty(); - } - - if (Dictionary.ContainsKey(pluginContext)) - { - return Dictionary[pluginContext].Keys; - } - - return Enumerable.Empty(); - } - - /// - /// Returns the default class for an status page. - /// - /// The status code. - /// The first status page found to the given states or null. - private ResponseItem GetStatusPage(int status) - { - if (Defaults == null) - { - return null; - } - - if (!Defaults.ContainsKey(status)) - { - return null; - } - - return Defaults[status]; - } - - /// - /// Returns the class for an status page. - /// - /// The status code. - /// The plugin context where the status pages are located. - /// The first status page found to the given states or null. - private ResponseItem GetStatusPage(int status, IPluginContext pluginContext) - { - if (pluginContext == null) - { - return null; - } - - if (!Dictionary.ContainsKey(pluginContext)) - { - return null; - } - - if (!Dictionary[pluginContext].ContainsKey(status)) - { - return null; - } - - return Dictionary[pluginContext][status]; - } - - /// - /// Creates a status page. - /// - /// The status message. - /// The status code. - /// The module context where the status pages are located or null for an undefined page (may be from another module) that matches the status code. - /// The created status page or null. - public IStatusPage CreateStatusPage(string massage, int status, IPluginContext pluginContext) - { - var responseItem = GetStatusPage(status, pluginContext); - - responseItem ??= GetStatusPage(status); - - if (responseItem == null) - { - return null; - } - - var statusPage = responseItem.StatusPageClass.Assembly.CreateInstance(responseItem.StatusPageClass?.FullName) as IStatusPage; - statusPage.StatusMessage = massage; - statusPage.StatusCode = status; - - return statusPage; - } - - /// - /// Removes all status pages associated with the specified plugin context. - /// - /// The context of the plugin that contains the status pages to remove. - public void Remove(IPluginContext pluginContext) - { - - } - - /// - /// Raises the AddStatusPage event. - /// - /// The status page. - private void OnAddStatusPage(IResourceContext statusPage) - { - AddStatusPage?.Invoke(this, statusPage); - } - - /// - /// Raises the RemoveComponent event. - /// - /// The status page. - private void OnRemoveStatusPage(IResourceContext statusPage) - { - RemoveStatusPage?.Invoke(this, statusPage); - } - - /// - /// Information about the component is collected and prepared for output in the log. - /// - /// The context of the plugin. - /// A list of log entries. - /// The shaft deep. - public void PrepareForLog(IPluginContext pluginContext, IList output, int deep) - { - foreach (var statusCode in GetStatusCodes(pluginContext)) - { - output.Add - ( - string.Empty.PadRight(4) + - InternationalizationManager.I18N - ( - "webexpress:responsemanager.statuspage", - statusCode - ) - ); - } - } - } -} diff --git a/src/WebExpress.WebCore/WebStatusPage/StatusMessage.cs b/src/WebExpress.WebCore/WebStatusPage/StatusMessage.cs new file mode 100644 index 0000000..694e6bf --- /dev/null +++ b/src/WebExpress.WebCore/WebStatusPage/StatusMessage.cs @@ -0,0 +1,23 @@ +namespace WebExpress.WebCore.WebStatusPage +{ + + /// + /// Represents a status message. + /// + public class StatusMessage + { + /// + /// Returns the message. + /// + public string Message { get; private set; } + + /// + /// Initializes a new instance of the class with the specified message. + /// + /// The message. + public StatusMessage(string message) + { + Message = message; + } + } +} diff --git a/src/WebExpress.WebCore/WebStatusPage/StatusPageContext.cs b/src/WebExpress.WebCore/WebStatusPage/StatusPageContext.cs new file mode 100644 index 0000000..8a4c416 --- /dev/null +++ b/src/WebExpress.WebCore/WebStatusPage/StatusPageContext.cs @@ -0,0 +1,52 @@ +using WebExpress.WebCore.WebApplication; +using WebExpress.WebCore.WebComponent; +using WebExpress.WebCore.WebEndpoint; +using WebExpress.WebCore.WebPlugin; + +namespace WebExpress.WebCore.WebStatusPage +{ + /// + /// Represents the context for a status page. + /// + public class StatusPageContext : IStatusPageContext + { + /// + /// Returns the associated plugin context. + /// + public IPluginContext PluginContext { get; internal set; } + + /// + /// Returns the associated application context. + /// + public IApplicationContext ApplicationContext { get; internal set; } + + /// + /// Returns the status id. + /// + public IComponentId StatusPageId { get; internal set; } + + /// + /// Returns the status code. + /// + public int StatusCode { get; internal set; } + + /// + /// Returns the status title. + /// + public string StatusTitle { get; internal set; } + + /// + /// Returns the status icon. + /// + public IRoute StatusIcon { get; internal set; } + + /// + /// Returns a string that represents the current object. + /// + /// A string that represents the current object. + public override string ToString() + { + return $"StatusPage: {StatusPageId.ToString()}"; + } + } +} diff --git a/src/WebExpress.WebCore/WebStatusPage/StatusPageManager.cs b/src/WebExpress.WebCore/WebStatusPage/StatusPageManager.cs new file mode 100644 index 0000000..a57c176 --- /dev/null +++ b/src/WebExpress.WebCore/WebStatusPage/StatusPageManager.cs @@ -0,0 +1,517 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using WebExpress.WebCore.Internationalization; +using WebExpress.WebCore.WebApplication; +using WebExpress.WebCore.WebAttribute; +using WebExpress.WebCore.WebComponent; +using WebExpress.WebCore.WebEndpoint; +using WebExpress.WebCore.WebLog; +using WebExpress.WebCore.WebMessage; +using WebExpress.WebCore.WebPage; +using WebExpress.WebCore.WebPlugin; +using WebExpress.WebCore.WebStatusPage.Model; + +namespace WebExpress.WebCore.WebStatusPage +{ + /// + /// Management of status pages. + /// + public class StatusPageManager : IStatusPageManager, ISystemComponent + { + private readonly IComponentHub _componentHub; + private readonly IHttpServerContext _httpServerContext; + private readonly StatusPageDictionary _dictionary = []; + private readonly Dictionary _defaults = []; + private static readonly Dictionary _delegateCache = []; + + /// + /// An event that fires when an status page is added. + /// + public event EventHandler AddStatusPage; + + /// + /// An event that fires when an status page is removed. + /// + public event EventHandler RemoveStatusPage; + + /// + /// Returns all status pages. + /// + public IEnumerable StatusPages => _dictionary.Values + .SelectMany(x => x.Values) + .SelectMany(x => x.Values) + .Select(x => x.StatusPageContext); + + /// + /// Initializes a new instance of the class. + /// + /// The component hub. + /// The reference to the context of the host. + [SuppressMessage("CodeQuality", "IDE0051:Remove unused private members", Justification = "Used via Reflection.")] + private StatusPageManager(IComponentHub componentHub, IHttpServerContext httpServerContext) + { + _componentHub = componentHub; + + _componentHub.PluginManager.AddPlugin += OnAddPlugin; + _componentHub.PluginManager.RemovePlugin += OnRemovePlugin; + _componentHub.ApplicationManager.AddApplication += OnAddApplication; + _componentHub.ApplicationManager.RemoveApplication += OnRemoveApplication; + + _httpServerContext = httpServerContext; + + _httpServerContext.Log.Debug + ( + I18N.Translate("webexpress.webcore:statuspagemanager.initialization") + ); + } + + /// + /// Discovers and binds status pages to an application. + /// + /// The context of the plugin whose status pages are to be associated. + private void Register(IPluginContext pluginContext) + { + if (_dictionary.ContainsKey(pluginContext)) + { + return; + } + + Register(pluginContext, _componentHub.ApplicationManager.GetApplications(pluginContext)); + } + + /// + /// Discovers and binds status pages to an application. + /// + /// The context of the application whose status pages are to be associated. + private void Register(IApplicationContext applicationContext) + { + foreach (var pluginContext in _componentHub.PluginManager.GetPlugins(applicationContext)) + { + if (_dictionary.TryGetValue(pluginContext, out var appDict) && appDict.ContainsKey(applicationContext)) + { + continue; + } + + Register(pluginContext, [applicationContext]); + } + } + + /// + /// Registers resources for a given plugin and application context. + /// + /// The plugin context. + /// The application context (optional). + private void Register(IPluginContext pluginContext, IEnumerable applicationContexts) + { + var assembly = pluginContext?.Assembly; + + foreach (var resource in assembly.GetTypes() + .Where(x => x.IsClass == true && x.IsSealed && x.IsPublic) + .Where(x => x.GetInterface(typeof(IStatusPage<>).Name) != null)) + { + var id = new ComponentId(resource.FullName); + var statusResponse = typeof(ResponseInternalServerError); + var icon = string.Empty; + var title = resource.Name; + var defaultItem = false; + + foreach (var customAttribute in resource.CustomAttributes + .Where(x => x.AttributeType.GetInterfaces().Contains(typeof(IStatusPageAttribute)))) + { + if (customAttribute.AttributeType.Name == typeof(StatusResponseAttribute<>).Name && customAttribute.AttributeType.Namespace == typeof(StatusResponseAttribute<>).Namespace) + { + statusResponse = customAttribute.AttributeType.GenericTypeArguments.FirstOrDefault(); + } + else if (customAttribute.AttributeType == typeof(TitleAttribute)) + { + title = customAttribute.ConstructorArguments.FirstOrDefault().Value?.ToString(); + } + else if (customAttribute.AttributeType == typeof(IconAttribute)) + { + icon = customAttribute.ConstructorArguments.FirstOrDefault().Value?.ToString(); + } + else if (customAttribute.AttributeType == typeof(DefaultAttribute)) + { + defaultItem = true; + } + } + + // assign the status pages to existing applications. + foreach (var applicationContext in applicationContexts) + { + if (statusResponse?.GetCustomAttribute()?.StatusCode == null) + { + _httpServerContext.Log.Debug + ( + I18N.Translate + ( + "webexpress.webcore:statuspagemanager.statuscodeless", + resource.Name, + applicationContext?.ApplicationId + ) + ); + } + + var stausIcon = !string.IsNullOrEmpty(icon) ? RouteEndpoint.Combine(applicationContext.ContextPath, icon) : null; + var statusCode = statusResponse.GetCustomAttribute().StatusCode; + var statusPageContext = new StatusPageContext() + { + StatusPageId = id, + PluginContext = pluginContext, + ApplicationContext = applicationContext, + StatusCode = statusCode, + StatusTitle = title, + StatusIcon = stausIcon + }; + + if (_dictionary.AddStatusPageItem(pluginContext, applicationContext, statusCode, new StatusPageItem() + { + StatusPageContext = statusPageContext, + PluginContext = pluginContext, + ApplicationContext = applicationContext, + StatusResponse = statusResponse, + StatusPageClass = resource + })) + { + OnAddStatusPage(statusPageContext); + + _httpServerContext.Log.Debug + ( + I18N.Translate + ( + "webexpress.webcore:statuspagemanager.register", + statusResponse, + resource.Name + ) + ); + } + else + { + _httpServerContext.Log.Debug + ( + I18N.Translate + ( + "webexpress.webcore:statuspagemanager.duplicat", + statusResponse, + resource.Name + ) + ); + } + + // default + if (!_defaults.ContainsKey(statusCode)) + { + _defaults.Add(statusCode, new StatusPageItem() + { + StatusPageContext = new StatusPageContext() + { + StatusPageId = id, + PluginContext = pluginContext, + ApplicationContext = applicationContext, + StatusCode = statusCode, + StatusTitle = title, + StatusIcon = stausIcon + }, + StatusPageClass = resource, + StatusResponse = statusResponse, + PluginContext = pluginContext + }); + } + else if (defaultItem) + { + _defaults[statusCode] = new StatusPageItem() + { + StatusPageContext = new StatusPageContext() + { + StatusPageId = id, + PluginContext = pluginContext, + ApplicationContext = applicationContext, + StatusCode = statusCode, + StatusTitle = title, + StatusIcon = stausIcon + }, + StatusPageClass = resource, + StatusResponse = statusResponse, + PluginContext = pluginContext, + }; + } + } + } + + Log(); + } + + /// + /// Determines the status page for a given application context and status type. + /// + /// The context of the application. + /// The status page class. + /// The context of the status page or null. + public IStatusPageContext GetStatusPage(IApplicationContext applicationContext, Type statusPageClass) + { + var item = _dictionary.GetStatusPageItem(applicationContext, statusPageClass); + + return item?.StatusPageContext; + } + + /// + /// Creates a status response. + /// + /// The status message. + /// The status code. + /// The application context where the status pages are located or null for an undefined page (may be from another application) that matches the status code. + /// The request. + /// The response or null. + public Response CreateStatusResponse(string message, int status, IApplicationContext applicationContext, Request request) + { + var statusPageItem = _dictionary.GetStatusPageItem(applicationContext, status); + + if (statusPageItem == null && _defaults.TryGetValue(status, out StatusPageItem value)) + { + statusPageItem = value; + } + + if (statusPageItem == null) + { + return status switch + { + 400 => new ResponseBadRequest(!string.IsNullOrWhiteSpace(message) ? new StatusMessage(message) : null), + 401 => new ResponseUnauthorized(!string.IsNullOrWhiteSpace(message) ? new StatusMessage(message) : null), + 404 => new ResponseNotFound(!string.IsNullOrWhiteSpace(message) ? new StatusMessage(message) : null), + 500 => new ResponseInternalServerError(!string.IsNullOrWhiteSpace(message) ? new StatusMessage(message) : null), + _ => new ResponseInternalServerError(!string.IsNullOrWhiteSpace(message) ? new StatusMessage(message) : null), + }; + } + + var pageInstance = ComponentActivator.CreateInstance + ( + statusPageItem.StatusPageClass, + statusPageItem.StatusPageContext, + _httpServerContext, + _componentHub, + new StatusMessage(message) + ); + var pageType = pageInstance.GetType(); + var pageContext = new PageContext(); + var renderContext = new RenderContext(pageInstance as IEndpoint, pageContext, request); + var visualTreeContext = new VisualTreeContext(renderContext); + + var visualTreeType = pageType.GetInterface(typeof(IStatusPage<>).Name).GetGenericArguments()[0]; + if (!_delegateCache.TryGetValue(pageType, out var del)) + { + // create and compile the expression + var renderContextParam = Expression.Parameter(typeof(IRenderContext), "renderContext"); + var visualTreeParam = Expression.Parameter(visualTreeType, "visualTree"); + var processMethod = pageType.GetMethod("Process", [typeof(IRenderContext), visualTreeType]); + var callProzessMethod = Expression.Call + ( + Expression.Constant(pageInstance), + processMethod, + renderContextParam, + visualTreeParam + ); + var lambda = Expression.Lambda(callProzessMethod, renderContextParam, visualTreeParam) + .Compile(); + + _delegateCache[pageType] = lambda; + del = lambda; + } + + // create visual tree instance + var visualTreeInstance = default(IVisualTree); + var flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; + var constructors = visualTreeType?.GetConstructors(flags); + + if (constructors != null) + { + foreach (var constructor in constructors.OrderByDescending(x => x.GetParameters().Length)) + { + // injection + var parameters = constructor.GetParameters(); + var hubProperties = _componentHub.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance); + var contextIdProperty = pageContext.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance) + .Where(x => x.PropertyType == typeof(IComponentId)) + .FirstOrDefault(); + + var parameterValues = parameters.Select(parameter => + parameter.ParameterType == typeof(IComponentHub) ? _componentHub : + parameter.ParameterType == typeof(IHttpServerContext) ? _httpServerContext : + parameter.ParameterType == typeof(IPageContext) ? pageContext : + parameter.ParameterType == typeof(IApplicationContext) ? pageContext?.ApplicationContext : + parameter.ParameterType == typeof(IComponentId) ? contextIdProperty?.GetValue(pageContext) : + hubProperties.Where(x => x.PropertyType == parameter.ParameterType) + .FirstOrDefault()? + .GetValue(_componentHub) ?? null + ).ToArray(); + + if (constructor.Invoke(parameterValues) is IVisualTree visualTree) + { + visualTreeInstance = visualTree; + } + } + } + else + { + visualTreeInstance = Activator.CreateInstance(visualTreeType) as IVisualTree; + } + + // execute the cached delegate + del.DynamicInvoke(renderContext, visualTreeInstance); + + var response = ComponentActivator.CreateInstance(statusPageItem.StatusResponse, _httpServerContext, _componentHub, new StatusMessage(message)); + var content = visualTreeInstance.Render(new VisualTreeContext(renderContext))?.ToString(); + + response.Content = content; + response.Header.ContentLength = content?.Length ?? 0; + + return response; + } + + /// + /// Removes all status pages associated with the specified plugin context. + /// + /// The context of the plugin that contains the status pages to remove. + internal void Remove(IPluginContext pluginContext) + { + if (pluginContext == null) + { + return; + } + + // the plugin has not been registered in the manager + if (!_dictionary.ContainsKey(pluginContext)) + { + return; + } + + _dictionary.Remove(pluginContext); + } + + /// + /// Removes all status pages associated with the specified application context. + /// + /// The context of the application that contains the status pages to remove. + internal void Remove(IApplicationContext applicationContext) + { + if (applicationContext == null) + { + return; + } + + foreach (var pluginDict in _dictionary.Values) + { + foreach (var appDict in pluginDict.Where(x => x.Key == applicationContext).Select(x => x.Value)) + { + foreach (var resourceItem in appDict.Values) + { + OnRemoveStatusPage(resourceItem.StatusPageContext); + resourceItem.Dispose(); + } + } + + pluginDict.Remove(applicationContext); + } + } + + /// + /// Raises the AddStatusPage event. + /// + /// The status page. + private void OnAddStatusPage(IStatusPageContext statusPage) + { + AddStatusPage?.Invoke(this, statusPage); + } + + /// + /// Raises the RemoveComponent event. + /// + /// The status page. + private void OnRemoveStatusPage(IStatusPageContext statusPage) + { + RemoveStatusPage?.Invoke(this, statusPage); + } + + /// + /// Raises the event when an plugin is added. + /// + /// The source of the event. + /// The context of the plugin being added. + private void OnAddPlugin(object sender, IPluginContext e) + { + Register(e); + } + + /// + /// Raises the event when a plugin is removed. + /// + /// The source of the event. + /// The context of the plugin being removed. + private void OnRemovePlugin(object sender, IPluginContext e) + { + Remove(e); + } + /// + /// Raises the event when an application is removed. + /// + /// The source of the event. + /// The context of the application being removed. + private void OnRemoveApplication(object sender, IApplicationContext e) + { + Remove(e); + } + + /// + /// Raises the event when an application is added. + /// + /// The source of the event. + /// The context of the application being added. + private void OnAddApplication(object sender, IApplicationContext e) + { + Register(e); + } + + /// + /// Information about the component is collected and prepared for output in the log. + /// + private void Log() + { + if (!StatusPages.Any()) + { + return; + } + + using var frame = new LogFrameSimple(_httpServerContext.Log); + var list = new List + { + I18N.Translate("webexpress.webcore:statuspagemanager.titel") + }; + + foreach (var statusPage in StatusPages) + { + list.Add + ( + I18N.Translate("webexpress.webcore:statuspagemanager.statuspage", statusPage.StatusCode) + ); + } + + _httpServerContext.Log.Info(string.Join(Environment.NewLine, list)); + } + + /// + /// Performs application-specific tasks related to sharing, returning, or resetting unmanaged resources. + /// + public void Dispose() + { + _componentHub.PluginManager.AddPlugin -= OnAddPlugin; + _componentHub.PluginManager.RemovePlugin -= OnRemovePlugin; + _componentHub.ApplicationManager.AddApplication -= OnAddApplication; + _componentHub.ApplicationManager.RemoveApplication -= OnRemoveApplication; + + GC.SuppressFinalize(this); + } + } +} diff --git a/src/WebExpress.WebCore/WebTask/ITask.cs b/src/WebExpress.WebCore/WebTask/ITask.cs index cfa99bb..0253c80 100644 --- a/src/WebExpress.WebCore/WebTask/ITask.cs +++ b/src/WebExpress.WebCore/WebTask/ITask.cs @@ -1,8 +1,12 @@ using System; +using WebExpress.WebCore.WebComponent; namespace WebExpress.WebCore.WebTask { - public interface ITask + /// + /// Represents a task that can be executed, monitored, and controlled. + /// + public interface ITask : IComponent { /// /// Event is triggered when the task is executed. @@ -34,11 +38,6 @@ public interface ITask /// string Message { get; set; } - /// - /// Initialization - /// - void Initialization(); - /// /// Starts the execution concurrently. /// diff --git a/src/WebExpress.WebCore/WebTask/ITaskManager.cs b/src/WebExpress.WebCore/WebTask/ITaskManager.cs new file mode 100644 index 0000000..105ce16 --- /dev/null +++ b/src/WebExpress.WebCore/WebTask/ITaskManager.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; +using WebExpress.WebCore.WebComponent; + +namespace WebExpress.WebCore.WebTask +{ + /// + /// Management of ad-hoc tasks. + /// + public interface ITaskManager : IComponentManager + { + /// + /// Returns the collection of tasks. + /// + IEnumerable Tasks { get; } + + /// + /// Checks if a task has already been created. + /// + /// The id of the task. + /// True if this task already exists, false otherwise. + bool ContainsTask(string id); + + /// + /// Returns an existing task. + /// + /// The id of the task. + /// The task or null. + ITask GetTask(string id); + + /// + /// Creates a new task or returns an existing task. + /// + /// The id of the task. + /// The event argument. + /// The task or null. + ITask CreateTask(string id, params object[] args); + + /// + /// Creates a new task or returns an existing task. + /// + /// The id of the task. + /// The event handler. + /// The event argument. + /// The task or null. + ITask CreateTask(string id, EventHandler handler, params object[] args); + + /// + /// Creates a new task or returns an existing task. + /// + /// The id of the task. + /// The event handler. + /// The event argument. + /// The task or null. + ITask CreateTask(string id, EventHandler handler, params object[] args) where T : Task; + + /// + /// Removes a task. + /// + /// The task. + void RemoveTask(ITask task); + } +} diff --git a/src/WebExpress.WebCore/WebTask/TaskDictionary.cs b/src/WebExpress.WebCore/WebTask/Model/TaskDictionary.cs similarity index 63% rename from src/WebExpress.WebCore/WebTask/TaskDictionary.cs rename to src/WebExpress.WebCore/WebTask/Model/TaskDictionary.cs index 6a0fa33..f6ed37b 100644 --- a/src/WebExpress.WebCore/WebTask/TaskDictionary.cs +++ b/src/WebExpress.WebCore/WebTask/Model/TaskDictionary.cs @@ -1,13 +1,13 @@ using System.Collections.Generic; -namespace WebExpress.WebCore.WebTask +namespace WebExpress.WebCore.WebTask.Model { /// /// Directory with the current tasks. /// Key = The task id. /// Value = The task. /// - internal class TaskDictionary : Dictionary + internal class TaskDictionary : Dictionary { } } diff --git a/src/WebExpress.WebCore/WebTask/Task.cs b/src/WebExpress.WebCore/WebTask/Task.cs index 8f84910..c65416f 100644 --- a/src/WebExpress.WebCore/WebTask/Task.cs +++ b/src/WebExpress.WebCore/WebTask/Task.cs @@ -1,16 +1,15 @@ using System; using System.Collections.Generic; using System.Threading; -using WebExpress.WebCore.WebComponent; namespace WebExpress.WebCore.WebTask { + /// + /// Represents a task that can be executed asynchronously. + /// public class Task : ITask { - /// - /// Internal management of progress. - /// - private int _Progress { get; set; } + private int _progress; /// /// Event is triggered when the task is executed. @@ -23,19 +22,19 @@ public class Task : ITask public event EventHandler Finish; /// - /// The id of the task. + /// Returns the id of the task. /// - public string Id { get; internal set; } + public string Id { get; private set; } /// /// Returns the state in which the task is located. /// - public TaskState State { get; internal set; } + public TaskState State { get; protected set; } = TaskState.Created; /// /// The arguments. /// - public ICollection Arguments { get; internal set; } + public ICollection Arguments { get; private set; } /// /// Thread termination of the task. @@ -47,8 +46,8 @@ public class Task : ITask /// public int Progress { - get => _Progress; - set => _Progress = Math.Min(value, 100); + get => _progress; + set => _progress = Math.Min(value, 100); } /// @@ -57,10 +56,14 @@ public int Progress public string Message { get; set; } /// - /// Initialization + /// Initializes a new instance of the class. /// - public virtual void Initialization() + /// The unique identifier for the task. + /// The arguments for the task. + public Task(string id, params object[] args) { + Id = id; + Arguments = args; } /// @@ -88,17 +91,17 @@ public void Run() { State = TaskState.Run; - this.Progress = 0; + Progress = 0; OnProcess(); - this.Progress = 100; + Progress = 100; State = TaskState.Finish; OnFinish(); - ComponentManager.TaskManager.RemoveTask(this); + WebEx.ComponentHub.TaskManager.RemoveTask(this); }), TokenSource.Token); } @@ -112,7 +115,15 @@ public void Cancel() State = TaskState.Canceled; - ComponentManager.TaskManager.RemoveTask(this); + WebEx.ComponentHub.TaskManager.RemoveTask(this); + } + + /// + /// Release of unmanaged resources reserved during use. + /// + public void Dispose() + { + Cancel(); } } } diff --git a/src/WebExpress.WebCore/WebTask/TaskEventArgs.cs b/src/WebExpress.WebCore/WebTask/TaskEventArgs.cs index 7a1c677..6d9fd3a 100644 --- a/src/WebExpress.WebCore/WebTask/TaskEventArgs.cs +++ b/src/WebExpress.WebCore/WebTask/TaskEventArgs.cs @@ -2,6 +2,9 @@ namespace WebExpress.WebCore.WebTask { + /// + /// Provides data for a task event. + /// public class TaskEventArgs : EventArgs { } diff --git a/src/WebExpress.WebCore/WebTask/TaskManager.cs b/src/WebExpress.WebCore/WebTask/TaskManager.cs index f46ed1f..86f38b4 100644 --- a/src/WebExpress.WebCore/WebTask/TaskManager.cs +++ b/src/WebExpress.WebCore/WebTask/TaskManager.cs @@ -1,44 +1,40 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using WebExpress.WebCore.Internationalization; using WebExpress.WebCore.WebComponent; -using WebExpress.WebCore.WebPlugin; +using WebExpress.WebCore.WebTask.Model; namespace WebExpress.WebCore.WebTask { /// /// Management of ad-hoc tasks. /// - public class TaskManager : IComponent, ISystemComponent + public class TaskManager : ITaskManager, ISystemComponent { - /// - /// Returns or sets the reference to the context of the host. - /// - public IHttpServerContext HttpServerContext { get; private set; } - - /// - /// Returns the directory in which the active jobs are listed. - /// - private TaskDictionary Dictionary { get; } = new TaskDictionary(); + private readonly IComponentHub _componentHub; + private readonly IHttpServerContext _httpServerContext; + private readonly TaskDictionary _dictionary = []; /// - /// Constructor + /// Returns the collection of tasks. /// - internal TaskManager() - { - } + public IEnumerable Tasks => _dictionary.Values; /// - /// Initialization + /// Initializes a new instance of the class. /// - /// The reference to the context of the host. - public void Initialization(IHttpServerContext context) + /// The component hub. + /// The reference to the context of the host. + [SuppressMessage("CodeQuality", "IDE0051:Remove unused private members", Justification = "Used via Reflection.")] + private TaskManager(IComponentHub componentHub, IHttpServerContext httpServerContext) { - HttpServerContext = context; + _componentHub = componentHub; + _httpServerContext = httpServerContext; - HttpServerContext.Log.Debug + _httpServerContext.Log.Debug ( - InternationalizationManager.I18N("webexpress:applicationmanager.initialization") + I18N.Translate("webexpress.webcore:applicationmanager.initialization") ); } @@ -49,7 +45,7 @@ public void Initialization(IHttpServerContext context) /// True if this task already exists, false otherwise. public bool ContainsTask(string id) { - return Dictionary.ContainsKey(id?.ToLower()); + return _dictionary.ContainsKey(id?.ToLower()); } /// @@ -59,9 +55,9 @@ public bool ContainsTask(string id) /// The task or null. public ITask GetTask(string id) { - if (Dictionary.ContainsKey(id?.ToLower())) + if (_dictionary.ContainsKey(id?.ToLower())) { - return Dictionary[id?.ToLower()]; + return _dictionary[id?.ToLower()]; } return null; @@ -77,17 +73,15 @@ public ITask CreateTask(string id, params object[] args) { var key = id?.ToLower(); - if (!Dictionary.ContainsKey(id)) + if (_dictionary.TryGetValue(id, out var value)) { - var task = new Task() { Id = id, State = TaskState.Created, Arguments = args }; - Dictionary.Add(key, task); - - task.Initialization(); - - return task; + return value; } - return Dictionary[id]; + var task = ComponentActivator.CreateInstance(_httpServerContext, _componentHub, [id, args]); + _dictionary.Add(key, task); + + return task; } /// @@ -109,23 +103,21 @@ public ITask CreateTask(string id, EventHandler handler, params o /// The event handler. /// The event argument. /// The task or null. - public ITask CreateTask(string id, EventHandler handler, params object[] args) where T : Task, new() + public ITask CreateTask(string id, EventHandler handler, params object[] args) where T : Task { var key = id?.ToLower(); - if (!Dictionary.ContainsKey(id)) + if (_dictionary.TryGetValue(id, out var value)) { - var task = new Task() { Id = id, State = TaskState.Created, Arguments = args }; - Dictionary.Add(key, task); + return value; + } - task.Initialization(); + var task = ComponentActivator.CreateInstance(_httpServerContext, _componentHub, [id, args]); + _dictionary.Add(key, task); - task.Process += handler; + task.Process += handler; - return task; - } - - return Dictionary[id]; + return task; } /// @@ -136,20 +128,15 @@ public void RemoveTask(ITask task) { var key = task?.Id.ToLower(); - if (Dictionary.ContainsKey(key)) - { - Dictionary.Remove(key); - } + _dictionary.Remove(key); } /// - /// Information about the component is collected and prepared for output in the log. + /// Release of unmanaged resources reserved during use. /// - /// The context of the plugin. - /// A list of log entries. - /// The shaft deep. - public void PrepareForLog(IPluginContext pluginContext, IList output, int deep) + public void Dispose() { + GC.SuppressFinalize(this); } } } diff --git a/src/WebExpress.WebCore/WebTask/TaskState.cs b/src/WebExpress.WebCore/WebTask/TaskState.cs index 33c1b5e..a5e8d6b 100644 --- a/src/WebExpress.WebCore/WebTask/TaskState.cs +++ b/src/WebExpress.WebCore/WebTask/TaskState.cs @@ -1,5 +1,9 @@ namespace WebExpress.WebCore.WebTask { + + /// + /// Represents the various states a task can be in. + /// public enum TaskState { /// diff --git a/src/WebExpress.WebCore/WebTheme/ITheme.cs b/src/WebExpress.WebCore/WebTheme/ITheme.cs new file mode 100644 index 0000000..f6e9118 --- /dev/null +++ b/src/WebExpress.WebCore/WebTheme/ITheme.cs @@ -0,0 +1,11 @@ +using WebExpress.WebCore.WebComponent; + +namespace WebExpress.WebCore.WebTheme +{ + /// + /// Represents a theme component in the web application. + /// + public interface ITheme : IComponent + { + } +} diff --git a/src/WebExpress.WebCore/WebTheme/IThemeContext.cs b/src/WebExpress.WebCore/WebTheme/IThemeContext.cs new file mode 100644 index 0000000..d58e729 --- /dev/null +++ b/src/WebExpress.WebCore/WebTheme/IThemeContext.cs @@ -0,0 +1,53 @@ +using WebExpress.WebCore.WebApplication; +using WebExpress.WebCore.WebComponent; +using WebExpress.WebCore.WebEndpoint; +using WebExpress.WebCore.WebPlugin; + +namespace WebExpress.WebCore.WebTheme +{ + /// + /// Represents the context for a theme in the web application. + /// + public interface IThemeContext : IContext + { + /// + /// Returns the theme id. + /// + IComponentId ThemeId { get; } + + /// + /// Returns the associated plugin context. + /// + IPluginContext PluginContext { get; } + + /// + /// Returns the corresponding application context. + /// + IApplicationContext ApplicationContext { get; } + + /// + /// Returns the image associated with the theme. + /// + IRoute Image { get; } + + /// + /// Returns the name of the theme. + /// + string Name { get; } + + /// + /// Returns the description of the theme. + /// + string Description { get; } + + /// + /// Returns the mode of the theme. + /// + ThemeMode ThemeMode { get; } + + /// + /// Returns the route resource for the css theme style. + /// + IRoute ThemeStyle { get; } + } +} diff --git a/src/WebExpress.WebCore/WebTheme/IThemeManager.cs b/src/WebExpress.WebCore/WebTheme/IThemeManager.cs new file mode 100644 index 0000000..21ee3ae --- /dev/null +++ b/src/WebExpress.WebCore/WebTheme/IThemeManager.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using WebExpress.WebCore.WebApplication; +using WebExpress.WebCore.WebComponent; + +namespace WebExpress.WebCore.WebTheme +{ + /// + /// Interface for managing themes within the web application. + /// + public interface IThemeManager : IComponentManager + { + /// + /// An event that fires when a theme is added. + /// + event EventHandler AddTheme; + + /// + /// An event that fires when a theme is removed. + /// + event EventHandler RemoveTheme; + + /// + /// Returns the collection of themes. + /// + IEnumerable Themes { get; } + + /// + /// Returns the theme contexts. + /// + /// The type of theme. + /// The application context. + /// An IEnumerable of theme contexts. + IEnumerable GetThemes(IApplicationContext applicationContext) + where TTheme : ITheme; + + /// + /// Returns the theme contexts. + /// + /// The application context. + /// The type of theme. + /// An IEnumerable of theme contexts. + IEnumerable GetThemes(IApplicationContext applicationContext, Type themeType); + + /// + /// Returns the theme associated with the specified theme context. + /// + /// The context of the theme to retrieve. + /// The theme associated with the specified context. + ITheme GetTheme(IThemeContext themeContext); + } +} diff --git a/src/WebExpress.WebCore/WebTheme/Model/ThemeItem.cs b/src/WebExpress.WebCore/WebTheme/Model/ThemeItem.cs new file mode 100644 index 0000000..a21cd80 --- /dev/null +++ b/src/WebExpress.WebCore/WebTheme/Model/ThemeItem.cs @@ -0,0 +1,49 @@ +using System; + +namespace WebExpress.WebCore.WebTheme.Model +{ + /// + /// Represents an theme item. + /// + public class ThemeItem : IDisposable + { + /// + /// Returns or sets the type of theme. + /// + public Type ThemeClass { get; set; } + + /// + /// Returns or sets the instance of the theme. + /// + public ITheme Instance { get; set; } + + /// + /// Returns the theme context. + /// + public IThemeContext ThemeContext { get; internal set; } + + /// + /// Initializes a new instance of the class. + /// + internal ThemeItem() + { + } + + /// + /// Performs application-specific tasks related to sharing, returning, or resetting unmanaged resources. + /// + public void Dispose() + { + + } + + /// + /// Convert the theme element to a string. + /// + /// The theme element in its string representation. + public override string ToString() + { + return $"Theme: '{ThemeContext?.ThemeId}'"; + } + } +} diff --git a/src/WebExpress.WebCore/WebTheme/Model/ThemeItemDictionary.cs b/src/WebExpress.WebCore/WebTheme/Model/ThemeItemDictionary.cs new file mode 100644 index 0000000..bfc38df --- /dev/null +++ b/src/WebExpress.WebCore/WebTheme/Model/ThemeItemDictionary.cs @@ -0,0 +1,201 @@ +using System.Collections.Generic; +using System.Linq; +using WebExpress.WebCore.WebApplication; +using WebExpress.WebCore.WebPlugin; + +namespace WebExpress.WebCore.WebTheme.Model +{ + /// + /// Represents a dictionary that stores theme items based on plugin and application contexts. + /// + internal class ThemeItemDictionary + { + private readonly Dictionary>> _dictionary = new(); + + /// + /// Returns all theme items. + /// + public IEnumerable All => _dictionary.Values + .SelectMany(x => x.Values) + .SelectMany(x => x); + + /// + /// Adds a theme item to the dictionary. + /// + /// The plugin context. + /// The application context. + /// The theme item. + /// True if the theme item was added successfully, false if an element with the same status code already exists. + public bool AddThemeItem(IPluginContext pluginContext, IApplicationContext applicationContext, ThemeItem themeItem) + { + var type = themeItem.ThemeClass; + + if (!typeof(ITheme).IsAssignableFrom(type)) + { + return false; + } + + if (!_dictionary.ContainsKey(pluginContext)) + { + _dictionary[pluginContext] = []; + } + + var appContextDict = _dictionary[pluginContext]; + + if (!appContextDict.TryGetValue(applicationContext, out List value)) + { + value = []; + appContextDict[applicationContext] = value; + } + + var assetList = value; + + assetList.RemoveAll(x => x.ThemeContext?.ThemeId == themeItem.ThemeContext.ThemeId); + assetList.Add(themeItem); + + return true; + } + + /// + /// Removes all resources associated with the specified plugin context. + /// + /// The context of the plugin that contains the resources to remove. + /// An enumeration of theme contexts that were removed. + public IEnumerable Remove(IPluginContext pluginContext) + { + if (pluginContext == null) + { + return []; + } + + // the plugin has not been registered in the manager + if (_dictionary.TryGetValue(pluginContext, out var value)) + { + var items = value.Values + .SelectMany(x => x) + .ToList(); + + foreach (var assetItem in items) + { + assetItem.Dispose(); + } + + _dictionary.Remove(pluginContext); + + return items.Select(x => x.ThemeContext); + } + + return []; + } + + /// + /// Removes all themes associated with the specified application context. + /// + /// The context of the application that contains the resources to remove. + /// An enumeration of theme contexts that were removed. + internal IEnumerable Remove(IApplicationContext applicationContext) + { + if (applicationContext == null) + { + return []; + } + + var removedThemes = new List(); + + foreach (var pluginDict in _dictionary.Values) + { + foreach (var themeList in pluginDict.Where(x => x.Key == applicationContext).Select(x => x.Value)) + { + foreach (var assetItem in themeList) + { + removedThemes.Add(assetItem.ThemeContext); + assetItem.Dispose(); + } + } + + pluginDict.Remove(applicationContext); + } + + return removedThemes; + } + + /// + /// Checks if the dictionary contains the specified plugin context. + /// + /// The plugin context to check. + /// True if the plugin context exists in the dictionary, otherwise false. + public bool ContainsPlugin(IPluginContext pluginContext) + { + return _dictionary.ContainsKey(pluginContext); + } + + /// + /// Checks if the dictionary contains the specified application context. + /// + /// The plugin context to check. + /// The application context to check. + /// True if the application context exists in the dictionary, otherwise false. + public bool ContainsApplication(IPluginContext pluginContext, IApplicationContext applicationContext) + { + if (_dictionary.TryGetValue(pluginContext, out var appDict) && appDict.ContainsKey(applicationContext)) + { + return true; + } + + return false; + } + + /// + /// Returns the theme items from the dictionary. + /// + /// The application context. + /// An IEnumerable of theme items + public IEnumerable GetThemeItems(IApplicationContext applicationContext) + { + if (_dictionary.ContainsKey(applicationContext?.PluginContext)) + { + var appContextDict = _dictionary[applicationContext?.PluginContext]; + + if (appContextDict.TryGetValue(applicationContext, out List value)) + { + var assetList = value; + + return assetList; + } + } + + return []; + } + + /// + /// Returns an enumeration of all containing theme contexts of a plugin. + /// + /// A context of a plugin whose theme are to be registered. + /// An enumeration of theme contexts. + public IEnumerable GetThemes(IPluginContext pluginContext) + { + if (_dictionary.TryGetValue(pluginContext, out var pluginResources)) + { + return pluginResources + .SelectMany(x => x.Value) + .Select(x => x.ThemeContext); + } + + return []; + } + + /// + /// Returns an enumeration of theme contextes. + /// + /// The context of the application. + /// An enumeration of theme contextes. + public IEnumerable GetThemes(IApplicationContext applicationContext) + { + return _dictionary.Values + .SelectMany(x => x.Values) + .SelectMany(x => x) + .Where(x => x.ThemeContext.ApplicationContext.Equals(applicationContext)) + .Select(x => x.ThemeContext); + } + } +} diff --git a/src/WebExpress.WebCore/WebTheme/ThemeContext.cs b/src/WebExpress.WebCore/WebTheme/ThemeContext.cs new file mode 100644 index 0000000..4fc09b2 --- /dev/null +++ b/src/WebExpress.WebCore/WebTheme/ThemeContext.cs @@ -0,0 +1,53 @@ +using WebExpress.WebCore.WebApplication; +using WebExpress.WebCore.WebComponent; +using WebExpress.WebCore.WebEndpoint; +using WebExpress.WebCore.WebPlugin; + +namespace WebExpress.WebCore.WebTheme +{ + /// + /// Represents the context for a theme in the web application. + /// + public class ThemeContext : IThemeContext + { + /// + /// Returns the theme id. + /// + public IComponentId ThemeId { get; internal set; } + + /// + /// Returns the associated plugin context. + /// + public IPluginContext PluginContext { get; internal set; } + + /// + /// Returns the corresponding application context. + /// + public IApplicationContext ApplicationContext { get; internal set; } + + /// + /// Returns the image associated with the theme. + /// + public IRoute Image { get; internal set; } + + /// + /// Returns the name of the theme. + /// + public string Name { get; internal set; } + + /// + /// Returns the description of the theme. + /// + public string Description { get; internal set; } + + /// + /// Returns the mode of the theme. + /// + public ThemeMode ThemeMode { get; internal set; } + + /// + /// Returns the route resource for the css theme style. + /// + public IRoute ThemeStyle { get; internal set; } + } +} diff --git a/src/WebExpress.WebCore/WebTheme/ThemeManager.cs b/src/WebExpress.WebCore/WebTheme/ThemeManager.cs new file mode 100644 index 0000000..928fc3a --- /dev/null +++ b/src/WebExpress.WebCore/WebTheme/ThemeManager.cs @@ -0,0 +1,353 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using WebExpress.WebCore.Internationalization; +using WebExpress.WebCore.WebApplication; +using WebExpress.WebCore.WebAttribute; +using WebExpress.WebCore.WebComponent; +using WebExpress.WebCore.WebEndpoint; +using WebExpress.WebCore.WebLog; +using WebExpress.WebCore.WebPlugin; +using WebExpress.WebCore.WebTheme.Model; + +namespace WebExpress.WebCore.WebTheme +{ + /// + /// Manages themes for the web application. + /// + public class ThemeManager : IThemeManager + { + private readonly IComponentHub _componentHub; + private readonly IHttpServerContext _httpServerContext; + private readonly ThemeItemDictionary _itemDictionary = new(); + + /// + /// Event triggered when a theme is added. + /// + public event EventHandler AddTheme; + + /// + /// Event triggered when a theme is removed. + /// + public event EventHandler RemoveTheme; + + /// + /// Returns the collection of themes. + /// + public IEnumerable Themes => _itemDictionary.All.Select(x => x.ThemeContext); + + /// + /// Initializes a new instance of the class. + /// + /// The component hub. + /// The reference to the context of the host. + [SuppressMessage("CodeQuality", "IDE0051:Remove unused private members", Justification = "Used via Reflection.")] + private ThemeManager(IComponentHub componentHub, IHttpServerContext httpServerContext) + { + _componentHub = componentHub; + + _componentHub.PluginManager.AddPlugin += OnAddPlugin; + _componentHub.PluginManager.RemovePlugin += OnRemovePlugin; + _componentHub.ApplicationManager.AddApplication += OnAddApplication; + _componentHub.ApplicationManager.RemoveApplication += OnRemoveApplication; + + _httpServerContext = httpServerContext; + + _httpServerContext.Log.Debug + ( + I18N.Translate("webexpress.webcore:thememanager.initialization") + ); + } + + /// + /// Returns the theme contexts. + /// + /// The type of theme. + /// The application context. + /// An IEnumerable of theme contexts. + public IEnumerable GetThemes(IApplicationContext applicationContext) + where TTheme : ITheme + { + return GetThemes(applicationContext, typeof(TTheme)); + } + + /// + /// Returns the theme contexts. + /// + /// The application context. + /// The type of theme. + /// An IEnumerable of theme contexts. + public IEnumerable GetThemes(IApplicationContext applicationContext, Type themeType) + { + return _itemDictionary.GetThemeItems(applicationContext) + .Where(x => x.ThemeClass == themeType) + .Select(x => x.ThemeContext); + } + + /// + /// Returns the theme associated with the specified theme context. + /// + /// The context of the theme to retrieve. + /// The theme associated with the specified context. + public ITheme GetTheme(IThemeContext themeContext) + { + return _itemDictionary.GetThemeItems(themeContext.ApplicationContext) + .Where(x => x.ThemeContext == themeContext) + .Select(x => x.Instance) + .FirstOrDefault(); + } + + /// + /// Discovers and binds resources to an application. + /// + /// The context of the plugin whose resources are to be associated. + private void Register(IPluginContext pluginContext) + { + if (_itemDictionary.ContainsPlugin(pluginContext)) + { + return; + } + + Register(pluginContext, _componentHub.ApplicationManager.GetApplications(pluginContext)); + } + + /// + /// Discovers and binds resources to an application. + /// + /// The context of the application whose resources are to be associated. + private void Register(IApplicationContext applicationContext) + { + foreach (var pluginContext in _componentHub.PluginManager.GetPlugins(applicationContext)) + { + if (_itemDictionary.ContainsApplication(pluginContext, applicationContext)) + { + continue; + } + + Register(pluginContext, [applicationContext]); + } + } + + /// + /// Registers resources for a given plugin and application context. + /// + /// The plugin context. + /// The application context (optional). + private void Register(IPluginContext pluginContext, IEnumerable applicationContexts) + { + var assembly = pluginContext?.Assembly; + var assemblName = assembly.GetName().Name; + var themeTypes = assembly.GetTypes().Where + ( + x => x.IsClass == true && + x.IsSealed && + x.IsPublic && + ( + x.GetInterface(typeof(ITheme).Name) != null + ) + ); + + foreach (var themeType in themeTypes) + { + var id = themeType.FullName?.ToLower(); + var image = default(string); + var name = default(string); + var description = default(string); + var mode = ThemeMode.Light; + var style = default(string); + + foreach (var customAttribute in themeType.CustomAttributes + .Where(x => x.AttributeType.GetInterfaces().Contains(typeof(IThemeAttribute)))) + { + if (customAttribute.AttributeType == typeof(ImageAttribute)) + { + image ??= customAttribute.ConstructorArguments.FirstOrDefault().Value?.ToString(); + } + else if (customAttribute.AttributeType == typeof(NameAttribute)) + { + name ??= customAttribute.ConstructorArguments.FirstOrDefault().Value?.ToString(); + } + else if (customAttribute.AttributeType == typeof(DescriptionAttribute)) + { + description ??= customAttribute.ConstructorArguments.FirstOrDefault().Value?.ToString(); + } + else if (customAttribute.AttributeType == typeof(ThemeModeAttribute)) + { + try + { + mode = Enum.Parse(customAttribute.ConstructorArguments.FirstOrDefault().Value?.ToString()); + } + catch + { + mode = ThemeMode.Light; + } + } + else if (customAttribute.AttributeType == typeof(ThemeStyleAttribute)) + { + style ??= customAttribute.ConstructorArguments.FirstOrDefault().Value?.ToString(); + } + } + + // assign the theme to existing applications + foreach (var applicationContext in applicationContexts) + { + var themeContext = new ThemeContext() + { + ThemeId = new ComponentId(id), + PluginContext = pluginContext, + ApplicationContext = applicationContext, + Name = name, + Description = description, + Image = image != null ? RouteEndpoint.Combine(applicationContext.ContextPath, image) : null, + ThemeMode = mode, + ThemeStyle = style != null ? RouteEndpoint.Combine(applicationContext.ContextPath, style) : null, + }; + + var themeItem = new ThemeItem() + { + ThemeClass = themeType, + ThemeContext = themeContext, + Instance = ComponentActivator.CreateInstance(themeType, themeContext, _httpServerContext, _componentHub, themeType), + }; + + if (_itemDictionary.AddThemeItem(pluginContext, applicationContext, themeItem)) + { + OnAddTheme(themeContext); + _httpServerContext?.Log.Debug( + I18N.Translate( + "webexpress.webcore:thememanager.addtheme", + id, + applicationContext.ApplicationId + ) + ); + } + } + } + + Log(); + } + + /// + /// Removes all resources associated with the specified plugin context. + /// + /// The context of the plugin that contains the resources to remove. + internal void Remove(IPluginContext pluginContext) + { + foreach (var themeContext in _itemDictionary.Remove(pluginContext)) + { + OnRemoveTheme(themeContext); + } + } + + /// + /// Removes all assets associated with the specified application context. + /// + /// The context of the application that contains the resources to remove. + internal void Remove(IApplicationContext applicationContext) + { + foreach (var assetContext in _itemDictionary.Remove(applicationContext)) + { + OnRemoveTheme(assetContext); + } + } + + /// + /// Raises the AddTheme event. + /// + /// The theme context. + private void OnAddTheme(IThemeContext themeContext) + { + AddTheme?.Invoke(this, themeContext); + } + + /// + /// Raises the RemoveTheme event. + /// + /// The theme context. + private void OnRemoveTheme(IThemeContext themeContext) + { + RemoveTheme?.Invoke(this, themeContext); + } + + /// + /// Raises the event when an plugin is added. + /// + /// The source of the event. + /// The context of the plugin being added. + private void OnAddPlugin(object sender, IPluginContext e) + { + Register(e); + } + + /// + /// Raises the event when a plugin is removed. + /// + /// The source of the event. + /// The context of the plugin being removed. + private void OnRemovePlugin(object sender, IPluginContext e) + { + Remove(e); + } + + /// + /// Raises the event when an application is added. + /// + /// The source of the event. + /// The context of the application being added. + private void OnAddApplication(object sender, IApplicationContext e) + { + Register(e); + } + + /// + /// Raises the event when an application is removed. + /// + /// The source of the event. + /// The context of the application being removed. + private void OnRemoveApplication(object sender, IApplicationContext e) + { + Remove(e); + } + + /// + /// Information about the component is collected and prepared for output in the log. + /// + private void Log() + { + if (!Themes.Any()) + { + return; + } + + using var frame = new LogFrameSimple(_httpServerContext.Log); + var list = new List + { + I18N.Translate("webexpress.webcore:thememanager.titel") + }; + + foreach (var eventHandlerContext in Themes) + { + list.Add + ( + I18N.Translate("webexpress.webcore:thememanager.theme", eventHandlerContext.ThemeId, eventHandlerContext.ApplicationContext?.ApplicationId) + ); + } + + _httpServerContext.Log.Info(string.Join(Environment.NewLine, list)); + } + + /// + /// Releases all resources used by the ThemeManager. + /// + public void Dispose() + { + _componentHub.PluginManager.AddPlugin -= OnAddPlugin; + _componentHub.PluginManager.RemovePlugin -= OnRemovePlugin; + _componentHub.ApplicationManager.AddApplication -= OnAddApplication; + _componentHub.ApplicationManager.RemoveApplication -= OnRemoveApplication; + + GC.SuppressFinalize(this); + } + } +} diff --git a/src/WebExpress.WebCore/WebTheme/ThemeMode.cs b/src/WebExpress.WebCore/WebTheme/ThemeMode.cs new file mode 100644 index 0000000..9c7deaf --- /dev/null +++ b/src/WebExpress.WebCore/WebTheme/ThemeMode.cs @@ -0,0 +1,18 @@ +namespace WebExpress.WebCore.WebTheme +{ + /// + /// Specifies the theme mode. + /// + public enum ThemeMode + { + /// + /// Light theme mode. + /// + Light, + + /// + /// Dark theme mode. + /// + Dark + } +} diff --git a/src/WebExpress.WebCore/WebUri/IUri.cs b/src/WebExpress.WebCore/WebUri/IUri.cs new file mode 100644 index 0000000..c8ecfa0 --- /dev/null +++ b/src/WebExpress.WebCore/WebUri/IUri.cs @@ -0,0 +1,122 @@ +using System.Collections.Generic; + +namespace WebExpress.WebCore.WebUri +{ + /// + /// An Uri represents a complete, fully qualified Uniform Resource Identifier (URI) that uniquely identifies a endpoint. + /// + /// This interface encapsulates all components of a typical URI, such as the scheme (e.g., "http", "https"), + /// the authority (e.g., "example.com"), path segments, query parameters, and fragment. It provides the external + /// address used for resource identification and linking (e.g., "http://example.com/users/123"). + /// + public interface IUri + { + /// + /// The scheme (e.g. Http, FTP). + /// + UriScheme Scheme { get; } + + /// + /// The authority (e.g. user@example.com:8080). + /// + UriAuthority Authority { get; } + + /// + /// The path (e.g. /over/there). + /// + IEnumerable PathSegments { get; } + + /// + /// Returns or sets the base path of the endpoint's URI. + /// The base path is included only when the endpoint class has the IncludeSubPaths attribute enabled. + /// For example, if the complete URI is "http://example.com/server/app/endpoint/extended", + /// the BasePath property will represent the "http://example.com/server/app/endpoint" portion of the URI. + /// + /// + /// The base path as an object, or null if the IncludeSubPaths attribute is not enabled. + /// + public IUri BasePath { get; set; } + + /// + /// The query part (e.g. ?title=Uniform_Resource_Identifier). + /// + IEnumerable Query { get; } + + /// + /// References a position within a resource (e.g. #Anchor). + /// + string Fragment { get; } + + /// + /// Returns the display string of the Uri + /// + string Display { get; } + + /// + /// Determines if the uri is empty. + /// + bool Empty { get; } + + /// + /// Determines if the Uri is the root. + /// + bool IsRoot { get; } + + /// + /// Checks if it is a relative uri. + /// + bool IsRelative { get; } + + /// + /// Retrieves a collection of variables represented as key-value pairs. + /// + IDictionary Parameters { get; } + + /// + /// Concatenates the given path segment to the current URI and returns a new instance of IUri with the updated path. + /// + /// The path segment to be concatenated with the existing URI. + /// A new IUri instance representing the URI after concatenation. + IUri Concat(string segment); + + /// + /// Concatenates the given path segment to the current URI and returns a new instance of IUri with the updated path. + /// + /// An array of path segments to be concatenated to the existing URI. + /// A new IUri instance representing the URI after concatenation. + IUri Concat(params IUriPathSegment[] segments); + + /// + /// Return a shortened uri containing n-elements. + /// count greater than 0 count elements are included + /// count less than 0 count elements are truncated + /// count = 0 an empty uri is returned + /// + /// The count of elements to include or truncate. + /// The sub uri with the specified number of elements. + IUri Take(int count); + + /// + /// Return a shortened uri by not including the first n elements. + /// count greater than 0 count elements are skipped + /// count less than or equals 0 an empty Uri is returned + /// + /// The count of elements to skip. + /// The sub uri after skipping the specified number of elements. + IUri Skip(int count); + + /// + /// Determines whether the given segment is part of the uri. + /// + /// The segment to be tested. + /// true if successful, false otherwise. + bool Contains(string segment); + + /// + /// Checks whether a given uri is part of that uri. + /// + /// The Uri to be checked. + /// true if part of the uri, false otherwise. + bool StartsWith(IUri uri); + } +} diff --git a/src/WebExpress.WebCore/WebUri/UriAuthority.cs b/src/WebExpress.WebCore/WebUri/UriAuthority.cs index 3afb5b8..8fdb5eb 100644 --- a/src/WebExpress.WebCore/WebUri/UriAuthority.cs +++ b/src/WebExpress.WebCore/WebUri/UriAuthority.cs @@ -31,7 +31,7 @@ public class UriAuthority public int? Port { get; set; } /// - /// Constructor + /// Initializes a new instance of the class. /// public UriAuthority() { @@ -39,7 +39,7 @@ public UriAuthority() } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The host. public UriAuthority(string host) @@ -48,7 +48,7 @@ public UriAuthority(string host) } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The host. /// The port. diff --git a/src/WebExpress.WebCore/WebUri/UriResource.cs b/src/WebExpress.WebCore/WebUri/UriEndpoint.cs similarity index 54% rename from src/WebExpress.WebCore/WebUri/UriResource.cs rename to src/WebExpress.WebCore/WebUri/UriEndpoint.cs index f1c9746..6348439 100644 --- a/src/WebExpress.WebCore/WebUri/UriResource.cs +++ b/src/WebExpress.WebCore/WebUri/UriEndpoint.cs @@ -6,10 +6,28 @@ namespace WebExpress.WebCore.WebUri { /// - /// A resource uri (e.g. /image.png). + /// An Uri represents a complete, fully qualified Uniform Resource Identifier (URI) that uniquely + /// identifies a endpoint (see RFC 3986). + /// This interface encapsulates all components of a typical URI, such as the scheme (e.g., "http", "https"), + /// the authority (e.g., "example.com"), path segments, query parameters, and fragment. It provides the external + /// address used for resource identification and linking (e.g., "http://example.com/users/123"). /// - public class UriResource + public partial class UriEndpoint : IUri { + /// + /// A regular expression to match URIs. + /// + /// A Regex object for matching URIs. + [GeneratedRegex("^([a-zA-Z0-9+.-]+):(?://(?:((?:[a-zA-Z0-9-._~!$&'()*+,;=:]|%[0-9a-fA-F]{2})*)@)?((?:[a-zA-Z0-9-._~!$&'()*+,;=]|%[0-9a-fA-F]{2})*)(?::(\\d*))?(.*)?)$")] + private static partial Regex UriRegex(); + + /// + /// Regular expression to match relative URIs. + /// + /// A Regex object for matching relative URIs. + [GeneratedRegex(@"^(\/([a-zA-Z0-9-+*%()=._/$]*))?(\?([a-zA-Z0-9-+*%()=._/$&]*))?(#([a-zA-Z0-9-+*%()=._/$]*))?$")] + private static partial Regex RelativeUriRegex(); + /// /// The scheme (e.g. Http, FTP). /// @@ -23,23 +41,23 @@ public class UriResource /// /// The path (e.g. /over/there). /// - public ICollection PathSegments { get; } = new List(); + public IEnumerable PathSegments { get; private set; } = [new UriPathSegmentRoot()]; /// - /// Returns the extended path. The extended path is the postfix of the resource's path. + /// Returns or sets the base path of the endpoint's URI. + /// The base path is included only when the endpoint class has the IncludeSubPaths attribute enabled. + /// For example, if the complete URI is "http://example.com/server/app/endpoint/extended", + /// the BasePath property will represent the "http://example.com/server/app/endpoint" portion of the URI. /// - public UriResource ExtendedPath - { - get - { - return new UriResource(Skip(ResourceRoot.PathSegments.Count()).PathSegments?.ToArray()); - } - } + /// + /// The base path as an object, or null if the IncludeSubPaths attribute is not enabled. + /// + public IUri BasePath { get; set; } /// - /// The query part (e.g. ?title=Uniform_Resource_Identifier&action=submit). + /// The query part (e.g. ?title=Uniform_Resource_Identifier). /// - public ICollection Query { get; } = new List(); + public IEnumerable Query { get; } = []; /// /// References a position within a resource (e.g. #Anchor). @@ -75,26 +93,6 @@ public virtual string Display /// public bool Empty => !PathSegments.Any(); - /// - /// Returns the root of the resource. - /// - public virtual UriResource ResourceRoot { get; set; } - - /// - /// Returns the root of the module. - /// - public virtual UriResource ModuleRoot { get; set; } - - /// - /// Returns the root of the application. - /// - public virtual UriResource ApplicationRoot { get; set; } - - /// - /// Returns the root of the server. - /// - public virtual UriResource ServerRoot { get; set; } - /// /// Determines if the Uri is the root. /// @@ -106,9 +104,9 @@ public virtual string Display public bool IsRelative => Authority == null; /// - /// Returns the variables. + /// Retrieves a collection of variables represented as key-value pairs. /// - public Dictionary Parameters + public IDictionary Parameters { get { @@ -130,20 +128,20 @@ public Dictionary Parameters } /// - /// Constructor + /// Initializes a new instance of the class. /// - public UriResource() + public UriEndpoint() { } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The scheme (e.g. Http, FTP). /// The authority (e.g. user@example.com:8080). /// The uri. - public UriResource(UriScheme scheme, UriAuthority authority, string uri) + public UriEndpoint(UriScheme scheme, UriAuthority authority, string uri) : this(uri) { Scheme = scheme; @@ -151,20 +149,20 @@ public UriResource(UriScheme scheme, UriAuthority authority, string uri) } /// - /// Constructor + /// Initializes a new instance of the class. /// - /// The uri. - public UriResource(string uri) + /// The uri. + public UriEndpoint(string uri) { - if (uri == null) return; + if (string.IsNullOrWhiteSpace(uri) || uri == "/") return; - if (Enum.GetNames(typeof(UriScheme)).Where(x => uri.StartsWith(x, StringComparison.OrdinalIgnoreCase)).Any()) + if (Enum.GetNames().Where(x => uri.StartsWith(x, StringComparison.OrdinalIgnoreCase)).Any()) { - var match = Regex.Match(uri, "^([a-z0-9+.-]+):(?://(?:((?:[a-z0-9-._~!$&'()*+,;=:]|%[0-9A-F]{2})*)@)?((?:[a-z0-9-._~!$&'()*+,;=]|%[0-9A-F]{2})*)(?::(\\d*))?(.*)?)$"); + var match = UriRegex().Match(uri); try { - Scheme = (UriScheme)Enum.Parse(typeof(UriScheme), match.Groups[1].Value, true); + Scheme = Enum.Parse(match.Groups[1].Value, true); } catch { @@ -182,174 +180,152 @@ public UriResource(string uri) } - var relativeMatch = Regex.Match(uri, @"^(\/([a-zA-Z0-9-+*%()=._/$]*))(#([a-zA-Z0-9-+*%()=._/$]*))?(\?(.*))?$"); - - PathSegments.Add(new UriPathSegmentRoot()); + var relativeMatch = RelativeUriRegex().Match(uri); foreach (var p in relativeMatch.Groups[2].Value.Split('/', StringSplitOptions.RemoveEmptyEntries)) { - PathSegments.Add(new UriPathSegmentConstant(p)); + PathSegments = PathSegments.Concat([new UriPathSegmentConstant(p)]); } - Fragment = relativeMatch.Groups[4].Success ? relativeMatch.Groups[4].Value : null; - - foreach (var q in relativeMatch.Groups[6].Success ? relativeMatch.Groups[6].Value?.Split('&') : Enumerable.Empty()) + foreach (var q in relativeMatch.Groups[4].Success ? relativeMatch.Groups[4].Value?.Split('&') : []) { var item = q.Split('='); - Query.Add(new UriQuerry(item[0], item.Length > 1 ? item[1] : null)); + Query = Query.Concat([new UriQuery(item[0], item.Length > 1 ? item[1] : null)]); } + + Fragment = relativeMatch.Groups[6].Success ? relativeMatch.Groups[6].Value : null; } /// /// Copy constructor /// /// The uri. - public UriResource(UriResource uri) + public UriEndpoint(IUri uri) { - Scheme = uri.Scheme; - Authority = uri.Authority; - PathSegments = uri.PathSegments.Select(x => x.Copy()).ToList(); - Query = uri.Query.Select(x => new UriQuerry(x.Key, x.Value)).ToList(); - Fragment = uri.Fragment; - ServerRoot = uri.ServerRoot; - ApplicationRoot = uri.ApplicationRoot; - ModuleRoot = uri.ModuleRoot; - ResourceRoot = uri.ResourceRoot; + Scheme = uri?.Scheme ?? UriScheme.Http; + Authority = uri?.Authority; + PathSegments = uri?.PathSegments.Select(x => x.Copy()) ?? []; + Query = uri?.Query.Select(x => new UriQuery(x.Key, x.Value)) ?? []; + Fragment = uri?.Fragment; } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The path segments. - public UriResource(params IUriPathSegment[] segments) + public UriEndpoint(params IUriPathSegment[] segments) { - PathSegments.Add(new UriPathSegmentRoot()); + PathSegments = PathSegments.Concat([new UriPathSegmentRoot()]); - foreach (var segment in segments.Where(x => !(x is UriPathSegmentRoot))) + foreach (var segment in segments.Where(x => x is not UriPathSegmentRoot)) { - PathSegments.Add(segment); + PathSegments = PathSegments.Concat([segment]); } } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The uri. /// The path segments. - public UriResource(UriResource uri, IEnumerable segments) + public UriEndpoint(IUri uri, IEnumerable segments) : this(uri.Scheme, uri.Authority, uri.Fragment, uri.Query, segments) { - ServerRoot = uri.ServerRoot; - ApplicationRoot = uri.ApplicationRoot; - ModuleRoot = uri.ModuleRoot; - ResourceRoot = uri.ResourceRoot; } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The uri. /// The path segments. /// Other segments. - public UriResource(UriResource uri, IEnumerable segments, IEnumerable extendedSegments) + public UriEndpoint(IUri uri, IEnumerable segments, IEnumerable extendedSegments) : this(uri.Scheme, uri.Authority, uri.Fragment, uri.Query, extendedSegments != null ? segments.Union(extendedSegments) : segments) { - ServerRoot = uri.ServerRoot; - ApplicationRoot = uri.ApplicationRoot; - ModuleRoot = uri.ModuleRoot; - ResourceRoot = uri.ResourceRoot; } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The scheme (e.g. Http, FTP). /// The authority (e.g. user@example.com:8080). /// References a position within a resource (e.g. #Anchor). - /// The query part (e.g. ?title=Uniform_Resource_Identifier&action=submit). + /// The query part (e.g. ?title=Uniform_Resource_Identifier). /// The path segments. - public UriResource(UriScheme scheme, UriAuthority authority, string fragment, IEnumerable query, IEnumerable segments) + public UriEndpoint(UriScheme scheme, UriAuthority authority, string fragment, IEnumerable query, IEnumerable segments) { Scheme = scheme; Authority = authority; - PathSegments.Add(new UriPathSegmentRoot()); - - foreach (var segment in segments != null ? segments.Where(x => !(x is UriPathSegmentRoot)) : Enumerable.Empty()) - { - PathSegments.Add(segment.Copy()); - } - - Query = query.Select(x => new UriQuerry(x.Key, x.Value)).ToList(); + PathSegments = PathSegments.Concat(segments?.Where(x => x is not UriPathSegmentRoot).Select(x => x.Copy()) ?? []); + Query = query.Select(x => new UriQuery(x.Key, x.Value)); Fragment = fragment; } /// - /// Adds a path element. + /// Concatenates the given path segment to the current URI and returns a new instance of IUri with the updated path. /// - /// The path to append. - /// The extended path. - public virtual UriResource Append(string path) + /// The path segment to be concatenated with the existing URI. + /// A new IUri instance representing the URI after concatenation. + public virtual IUri Concat(string segment) { - if (string.IsNullOrWhiteSpace(path)) + if (string.IsNullOrWhiteSpace(segment)) { return this; } - var copy = new UriResource(this); - - foreach (var p in path.Split('/', StringSplitOptions.RemoveEmptyEntries)) - { - copy.PathSegments.Add(new UriPathSegmentConstant(p)); - } + var copy = new UriEndpoint((IUri)this); + copy.PathSegments = copy.PathSegments + .Concat(segment.Split('/', StringSplitOptions.RemoveEmptyEntries) + .Select(x => new UriPathSegmentConstant(x))); return copy; } /// - /// Adds a path element. + /// Concatenates the given path segment to the current URI and returns a new instance of IUri with the updated path. /// - /// The path to append. - /// The extended path. - public virtual UriResource Append(IUriPathSegment path) + /// An array of path segments to be concatenated to the existing URI. + /// A new IUri instance representing the URI after concatenation. + public virtual IUri Concat(params IUriPathSegment[] segments) { - if (path == null || path.IsEmpty) + if (segments.Length == 0) { return this; } - var copy = new UriResource(this); - - copy.PathSegments.Add(path); + var copy = new UriEndpoint((IUri)this); + copy.PathSegments = copy.PathSegments + .Select(x => x.Copy()); return copy; } /// /// Return a shortened uri containing n-elements. - /// count > 0 count elements are included - /// count < 0 count elements are truncated + /// count greater than 0 count elements are included + /// count less than 0 count elements are truncated /// count = 0 an empty uri is returned /// - /// The count. - /// The sub uri. - public virtual UriResource Take(int count) + /// The count of elements to include or truncate. + /// The sub uri with the specified number of elements. + public virtual IUri Take(int count) { - var copy = new UriResource(this); + var copy = new UriEndpoint((IUri)this); var path = copy.PathSegments.ToList(); - copy.PathSegments.Clear(); + copy.PathSegments = []; if (count == 0) { - return new UriResource(); + return new UriEndpoint(); } else if (count > 0) { - (copy.PathSegments as List).AddRange(path.Take(count)); + copy.PathSegments = copy.PathSegments.Concat(path.Take(count)); } else if (count < 0 && Math.Abs(count) < path.Count) { - (copy.PathSegments as List).AddRange(path.Take(path.Count + count)); + copy.PathSegments = copy.PathSegments.Concat(path.Take(path.Count + count)); } else { @@ -361,28 +337,28 @@ public virtual UriResource Take(int count) /// /// Return a shortened uri by not including the first n elements. - /// count > 0 count elements are skipped - /// count <= 0 an empty Uri is returned + /// count greater than 0 count elements are skipped + /// count less than or equals 0 an empty Uri is returned /// - /// The count. - /// The sub uri. - public UriResource Skip(int count) + /// The count of elements to skip. + /// The sub uri after skipping the specified number of elements. + public IUri Skip(int count) { - if (count >= PathSegments.Count) + if (count >= PathSegments.Count()) { return null; } if (count > 0) { - var copy = new UriResource(this); + var copy = new UriEndpoint((IUri)this); var path = copy.PathSegments.ToList(); - copy.PathSegments.Clear(); - (copy.PathSegments as List).AddRange(path.Skip(count)); + copy.PathSegments = []; + copy.PathSegments = copy.PathSegments.Concat(path.Skip(count)); return copy; } - return new UriResource(this); + return new UriEndpoint((IUri)this); } /// @@ -400,17 +376,17 @@ public virtual bool Contains(string segment) /// /// The Uri to be checked. /// true if part of the uri, false otherwise. - public bool StartsWith(UriResource uri) + public bool StartsWith(IUri uri) { return ToString().StartsWith(uri.ToString()); } /// - /// Creates a new resource uri and fills it with the given parameters. + /// Creates a new endpoint uri and fills it with the given parameters. /// /// The parameters that fill in the variable parts of the uri. - /// A new resource uri with the populated parameters. - public UriResource SetParameters(params WebMessage.Parameter[] parameters) + /// A new endpoint uri with the populated parameters. + public IUri SetParameters(params WebMessage.Parameter[] parameters) { var pathSegments = PathSegments.AsEnumerable(); @@ -431,49 +407,7 @@ public UriResource SetParameters(params WebMessage.Parameter[] parameters) }); } - return new UriResource(this, pathSegments); - } - - /// - /// Converts the uri to a string. - /// - /// A string that represents the current uri. - public override string ToString() - { - var defaultPort = Scheme switch - { - UriScheme.Http => 80, - UriScheme.Https => 443, - UriScheme.FTP => 21, - UriScheme.Ldap => 389, - UriScheme.Ldaps => 636, - _ => -1 - - }; - - var scheme = Scheme.ToString("g").ToLower() + ":"; - var authority = Authority?.ToString(defaultPort); - var uri = "/" + string.Join - ( - "/", - PathSegments.Where(x => !(x is UriPathSegmentRoot)).Select(x => x.ToString()) - ); - - if (!string.IsNullOrWhiteSpace(Fragment)) - { - uri += "#" + Fragment; - } - - if (Query.Any()) - { - uri += "?" + string.Join("&", Query.Select(x => $"{x.Key}={x.Value}")); - } - - return Scheme switch - { - UriScheme.Mailto => string.Format("{0}{1}", scheme, authority), - _ => IsRelative ? uri : string.Format("{0}{1}{2}", scheme, authority, uri), - }; + return new UriEndpoint(this, pathSegments); } /// @@ -481,15 +415,16 @@ public override string ToString() /// /// The uris to be combine. /// A combined uri. - public static UriResource Combine(params string[] uris) + public static IUri Combine(params string[] uris) { - var uri = new UriResource(); + var copy = new UriEndpoint(); - (uri.PathSegments as List).AddRange(uris.Where(x => !string.IsNullOrWhiteSpace(x)) - .SelectMany(x => x.Split('/', StringSplitOptions.RemoveEmptyEntries)) - .Select(x => new UriPathSegmentConstant(x) as IUriPathSegment)); + copy.PathSegments = copy.PathSegments + .Concat(uris.Where(x => !string.IsNullOrWhiteSpace(x)) + .SelectMany(x => x.Split('/', StringSplitOptions.RemoveEmptyEntries)) + .Select(x => new UriPathSegmentConstant(x) as IUriPathSegment)); - return uri; + return copy; } /// @@ -497,12 +432,13 @@ public static UriResource Combine(params string[] uris) /// /// The uris to be combine. /// A combined uri. - public static UriResource Combine(params UriResource[] uris) + public static IUri Combine(params IUri[] uris) { - var uri = new UriResource(uris.FirstOrDefault()); - (uri.PathSegments as List).AddRange(uris.Skip(1).SelectMany(x => x.PathSegments.Skip(1))); + var copy = new UriEndpoint(uris.FirstOrDefault()); + copy.PathSegments = copy.PathSegments + .Concat(uris.Skip(1).SelectMany(x => x.PathSegments.Skip(1))); - return uri; + return copy; } /// @@ -511,12 +447,14 @@ public static UriResource Combine(params UriResource[] uris) /// The first uri to be combine. /// The uris to be combine. /// A combined uri. - public static UriResource Combine(UriResource uri, params string[] uris) + public static IUri Combine(IUri uri, params string[] uris) { - var copy = new UriResource(uri); - (copy.PathSegments as List).AddRange(uris.Where(x => !string.IsNullOrWhiteSpace(x)) - .SelectMany(x => x.Split('/', StringSplitOptions.RemoveEmptyEntries)) - .Select(x => new UriPathSegmentConstant(x) as IUriPathSegment)); + var copy = new UriEndpoint(uri); + copy.PathSegments = copy.PathSegments + .Concat(uris.Where(x => !string.IsNullOrWhiteSpace(x)) + .SelectMany(x => x.Split('/', StringSplitOptions.RemoveEmptyEntries)) + .Select(x => new UriPathSegmentConstant(x) as IUriPathSegment)); + return copy; } @@ -524,9 +462,52 @@ public static UriResource Combine(UriResource uri, params string[] uris) /// Converts a resource uri to a normal uri. /// /// The uri to convert. - public static implicit operator string(UriResource uri) + public static implicit operator string(UriEndpoint uri) { return uri?.ToString(); } + + /// + /// Converts the uri to a string. + /// + /// A string that represents the current uri. + public override string ToString() + { + var defaultPort = Scheme switch + { + UriScheme.Http => 80, + UriScheme.Https => 443, + UriScheme.FTP => 21, + UriScheme.Ldap => 389, + UriScheme.Ldaps => 636, + _ => -1 + + }; + + var scheme = Scheme.ToString("g").ToLower() + ":"; + var authority = Authority?.ToString(defaultPort); + var uri = "/" + string.Join + ( + "/", + PathSegments.Where(x => x is not UriPathSegmentRoot) + .Select(x => x.ToString().TrimStart('/')) + ).TrimEnd('/'); + + if (Query.Any()) + { + uri += "?" + string.Join("&", Query.Select(x => x.ToString())); + } + + if (!string.IsNullOrWhiteSpace(Fragment)) + { + uri += "#" + Fragment; + } + + return Scheme switch + { + UriScheme.Mailto => string.Format("{0}{1}", scheme, authority), + _ => IsRelative ? uri : string.Format("{0}{1}{2}", scheme, authority, uri), + }; + } } } \ No newline at end of file diff --git a/src/WebExpress.WebCore/WebUri/UriFragment.cs b/src/WebExpress.WebCore/WebUri/UriFragment.cs index f3325b5..fb4afd4 100644 --- a/src/WebExpress.WebCore/WebUri/UriFragment.cs +++ b/src/WebExpress.WebCore/WebUri/UriFragment.cs @@ -3,10 +3,10 @@ namespace WebExpress.WebCore.WebUri /// /// Uri which consists only of the fragment (e.g. #). /// - public class UriFragment : UriResource + public class UriFragment : UriEndpoint { /// - /// Constructor + /// Initializes a new instance of the class. /// public UriFragment() { diff --git a/src/WebExpress.WebCore/WebUri/UriPathSegmentConstant.cs b/src/WebExpress.WebCore/WebUri/UriPathSegmentConstant.cs index cb3785b..9abbc45 100644 --- a/src/WebExpress.WebCore/WebUri/UriPathSegmentConstant.cs +++ b/src/WebExpress.WebCore/WebUri/UriPathSegmentConstant.cs @@ -5,7 +5,7 @@ namespace WebExpress.WebCore.WebUri { /// - /// constant path segment. + /// Constant path segment. /// public class UriPathSegmentConstant : IUriPathSegmentConstant { @@ -35,7 +35,7 @@ public class UriPathSegmentConstant : IUriPathSegmentConstant public bool IsEmpty => string.IsNullOrWhiteSpace(Value) || Value.Equals("/"); /// - /// Constructor + /// Initializes a new instance of the class. /// /// The name. /// The tag or null @@ -45,7 +45,7 @@ public UriPathSegmentConstant(string value, object tag = null) } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The name. /// The display text. @@ -107,7 +107,7 @@ public virtual bool Equals(IUriPathSegment obj) /// The culture. public virtual string GetDisplay(CultureInfo culture) { - return InternationalizationManager.I18N(culture, Display); + return I18N.Translate(culture, Display); } /// diff --git a/src/WebExpress.WebCore/WebUri/UriPathSegmentRoot.cs b/src/WebExpress.WebCore/WebUri/UriPathSegmentRoot.cs index d9ab3ed..a27075c 100644 --- a/src/WebExpress.WebCore/WebUri/UriPathSegmentRoot.cs +++ b/src/WebExpress.WebCore/WebUri/UriPathSegmentRoot.cs @@ -5,12 +5,12 @@ namespace WebExpress.WebCore.WebUri { /// - /// constant path segment. + /// Represents the root segment of a URI path. /// public class UriPathSegmentRoot : IUriPathSegment { /// - /// Returns or sets the id. + /// Returns the ID of the segment. /// public string Id => "ROOT"; @@ -30,16 +30,15 @@ public class UriPathSegmentRoot : IUriPathSegment public object Tag { get; set; } /// - /// Checks for empty path segment. + /// Returns a value indicating whether the path segment is empty. /// public bool IsEmpty => false; /// - /// Constructor + /// Initializes a new instance of the class. /// - /// The name. /// The display text. - /// The tag or null + /// The tag or null. public UriPathSegmentRoot(string display = null, object tag = null) { Value = "/"; @@ -48,10 +47,10 @@ public UriPathSegmentRoot(string display = null, object tag = null) } /// - /// Checks whether the node matches the path element. + /// Checks whether the node matches the specified path element. /// /// The value to check. - /// True if the path element matched, false otherwise. + /// True if the path element matches, false otherwise. public bool IsMatched(string value) { if (string.IsNullOrWhiteSpace(value)) @@ -64,19 +63,19 @@ public bool IsMatched(string value) } /// - /// Make a deep copy. + /// Creates a deep copy of the current segment. /// - /// The copy. + /// A copy of the current segment. public virtual IUriPathSegment Copy() { return new UriPathSegmentRoot(Display, Tag); } /// - /// Compare the object. + /// Compares the current segment with another object. /// - /// The comparison object. - /// true if equals, false otherwise + /// The object to compare with. + /// True if the objects are equal, false otherwise. public virtual bool Equals(IUriPathSegment obj) { if (obj == null) @@ -84,16 +83,17 @@ public virtual bool Equals(IUriPathSegment obj) return false; } - return obj is UriPathSegmentRoot segment; + return obj is UriPathSegmentRoot; } /// - /// Returns or sets the display text. + /// Returns the display text for the specified culture. /// /// The culture. + /// The display text for the specified culture. public virtual string GetDisplay(CultureInfo culture) { - return InternationalizationManager.I18N(culture, Display); + return I18N.Translate(culture, Display); } /// diff --git a/src/WebExpress.WebCore/WebUri/UriPathSegmentVariable.cs b/src/WebExpress.WebCore/WebUri/UriPathSegmentVariable.cs index 19755c4..88325b1 100644 --- a/src/WebExpress.WebCore/WebUri/UriPathSegmentVariable.cs +++ b/src/WebExpress.WebCore/WebUri/UriPathSegmentVariable.cs @@ -47,7 +47,7 @@ public abstract class UriPathSegmentVariable : IUriPathSegmentVariable public bool IsEmpty => string.IsNullOrWhiteSpace(VariableName) || VariableName.Equals("/"); /// - /// Constructor + /// Initializes a new instance of the class. /// /// The name. /// The tag or null @@ -57,7 +57,7 @@ public UriPathSegmentVariable(string name, object tag = null) } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The name. /// The display text. @@ -131,7 +131,7 @@ public virtual bool Equals(IUriPathSegment obj) /// The culture. public virtual string GetDisplay(CultureInfo culture) { - return string.Format(InternationalizationManager.I18N(culture, Display), Value); + return string.Format(I18N.Translate(culture, Display), Value); } /// diff --git a/src/WebExpress.WebCore/WebUri/UriPathSegmentVariableDouble.cs b/src/WebExpress.WebCore/WebUri/UriPathSegmentVariableDouble.cs index bd1bcd4..d165294 100644 --- a/src/WebExpress.WebCore/WebUri/UriPathSegmentVariableDouble.cs +++ b/src/WebExpress.WebCore/WebUri/UriPathSegmentVariableDouble.cs @@ -8,9 +8,9 @@ namespace WebExpress.WebCore.WebUri public class UriPathSegmentVariableDouble : UriPathSegmentVariable { /// - /// Constructor + /// Initializes a new instance of the class. /// - /// The path text. + /// The name. /// The tag or null public UriPathSegmentVariableDouble(string name, object tag = null) : base(name, tag) @@ -23,9 +23,9 @@ public UriPathSegmentVariableDouble(string name, object tag = null) } /// - /// Constructor + /// Initializes a new instance of the class. /// - /// The path text. + /// The name. /// The display text. /// The tag or null public UriPathSegmentVariableDouble(string name, string display, object tag = null) @@ -39,7 +39,7 @@ public UriPathSegmentVariableDouble(string name, string display, object tag = nu } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The path segment to copy. public UriPathSegmentVariableDouble(UriPathSegmentVariableDouble segment) diff --git a/src/WebExpress.WebCore/WebUri/UriPathSegmentVariableGuid.cs b/src/WebExpress.WebCore/WebUri/UriPathSegmentVariableGuid.cs index eea1871..0b536f9 100644 --- a/src/WebExpress.WebCore/WebUri/UriPathSegmentVariableGuid.cs +++ b/src/WebExpress.WebCore/WebUri/UriPathSegmentVariableGuid.cs @@ -6,14 +6,25 @@ namespace WebExpress.WebCore.WebUri { /// - /// Variable path segment. + /// Represents a URI path segment variable for GUIDs. /// public class UriPathSegmentVariableGuid : UriPathSegmentVariable { /// /// The display formats of the guid. /// - public enum Format { Full, Simple } + public enum Format + { + /// + /// Full format of the guid. + /// + Full, + + /// + /// Simple format of the guid. + /// + Simple + } /// /// Returns the display format. @@ -21,7 +32,7 @@ public enum Format { Full, Simple } public Format DisplayFormat { get; private set; } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The path text. /// The tag or null @@ -31,9 +42,9 @@ public UriPathSegmentVariableGuid(string name, object tag = null) } /// - /// Constructor + /// Initializes a new instance of the class. /// - /// The path text. + /// The name. /// The display text. /// The tag or null public UriPathSegmentVariableGuid(string name, string display, object tag = null) @@ -42,9 +53,9 @@ public UriPathSegmentVariableGuid(string name, string display, object tag = null } /// - /// Constructor + /// Initializes a new instance of the class. /// - /// The path text. + /// The name. /// The display text. /// The display format. /// The tag or null @@ -57,7 +68,7 @@ public UriPathSegmentVariableGuid(string name, string display, Format displayFor } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The path segment to copy. public UriPathSegmentVariableGuid(UriPathSegmentVariableGuid segment) @@ -114,7 +125,7 @@ public override string GetDisplay(CultureInfo culture) return string.Format ( - InternationalizationManager.I18N(culture, Display), + I18N.Translate(culture, Display), guid ); } diff --git a/src/WebExpress.WebCore/WebUri/UriPathSegmentVariableInt.cs b/src/WebExpress.WebCore/WebUri/UriPathSegmentVariableInt.cs index 9b8bbe2..a2a3b04 100644 --- a/src/WebExpress.WebCore/WebUri/UriPathSegmentVariableInt.cs +++ b/src/WebExpress.WebCore/WebUri/UriPathSegmentVariableInt.cs @@ -8,7 +8,7 @@ namespace WebExpress.WebCore.WebUri public class UriPathSegmentVariableInt : UriPathSegmentVariable { /// - /// Constructor + /// Initializes a new instance of the class. /// /// The path text. /// The tag or null @@ -23,9 +23,9 @@ public UriPathSegmentVariableInt(string name, object tag = null) } /// - /// Constructor + /// Initializes a new instance of the class. /// - /// The path text. + /// The name. /// The display text. /// The tag or null public UriPathSegmentVariableInt(string name, string display, object tag = null) @@ -39,7 +39,7 @@ public UriPathSegmentVariableInt(string name, string display, object tag = null) } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The path segment to copy. public UriPathSegmentVariableInt(UriPathSegmentVariableInt segment) diff --git a/src/WebExpress.WebCore/WebUri/UriPathSegmentVariableString.cs b/src/WebExpress.WebCore/WebUri/UriPathSegmentVariableString.cs index 7136903..ba56c3f 100644 --- a/src/WebExpress.WebCore/WebUri/UriPathSegmentVariableString.cs +++ b/src/WebExpress.WebCore/WebUri/UriPathSegmentVariableString.cs @@ -8,9 +8,9 @@ namespace WebExpress.WebCore.WebUri public class UriPathSegmentVariableString : UriPathSegmentVariable { /// - /// Constructor + /// Initializes a new instance of the class. /// - /// The path text. + /// The name. /// The tag or null public UriPathSegmentVariableString(string name, object tag = null) : base(name, tag) @@ -23,9 +23,9 @@ public UriPathSegmentVariableString(string name, object tag = null) } /// - /// Constructor + /// Initializes a new instance of the class. /// - /// The path text. + /// The name. /// The display text. /// The tag or null public UriPathSegmentVariableString(string name, string display, object tag = null) @@ -39,7 +39,7 @@ public UriPathSegmentVariableString(string name, string display, object tag = nu } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The path segment to copy. public UriPathSegmentVariableString(UriPathSegmentVariableString segment) diff --git a/src/WebExpress.WebCore/WebUri/UriPathSegmentVariableUInt.cs b/src/WebExpress.WebCore/WebUri/UriPathSegmentVariableUInt.cs index 3f72a2d..b7ad4c7 100644 --- a/src/WebExpress.WebCore/WebUri/UriPathSegmentVariableUInt.cs +++ b/src/WebExpress.WebCore/WebUri/UriPathSegmentVariableUInt.cs @@ -8,9 +8,9 @@ namespace WebExpress.WebCore.WebUri public class UriPathSegmentVariableUInt : UriPathSegmentVariable { /// - /// Constructor + /// Initializes a new instance of the class. /// - /// The path text. + /// The name. /// The tag or null public UriPathSegmentVariableUInt(string name, object tag = null) : base(name, tag) @@ -23,9 +23,9 @@ public UriPathSegmentVariableUInt(string name, object tag = null) } /// - /// Constructor + /// Initializes a new instance of the class. /// - /// The path text. + /// The name. /// The display text. /// The tag or null public UriPathSegmentVariableUInt(string name, string display, object tag = null) @@ -39,7 +39,7 @@ public UriPathSegmentVariableUInt(string name, string display, object tag = null } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The path segment to copy. public UriPathSegmentVariableUInt(UriPathSegmentVariableUInt segment) diff --git a/src/WebExpress.WebCore/WebUri/UriQuerry.cs b/src/WebExpress.WebCore/WebUri/UriQuery.cs similarity index 54% rename from src/WebExpress.WebCore/WebUri/UriQuerry.cs rename to src/WebExpress.WebCore/WebUri/UriQuery.cs index 435f3e4..54f6982 100644 --- a/src/WebExpress.WebCore/WebUri/UriQuerry.cs +++ b/src/WebExpress.WebCore/WebUri/UriQuery.cs @@ -1,9 +1,9 @@ namespace WebExpress.WebCore.WebUri { /// - /// The query part (e.g. ?title=Uniform_Resource_Identifier&action=submit). + /// The query part (e.g. ?title=Uniform_Resource_Identifier). /// - public class UriQuerry + public class UriQuery { /// /// Returns the key. @@ -16,14 +16,23 @@ public class UriQuerry public string Value { get; protected set; } /// - /// Constructor + /// Initializes a new instance of the class. /// /// The key. /// The value. - public UriQuerry(string key, string value) + public UriQuery(string key, string value) { Key = key; Value = value; } + + /// + /// Converts the query to a string. + /// + /// A string that represents the current query. + public override string ToString() + { + return $"{Key}={Value}"; + } } } \ No newline at end of file diff --git a/src/WebExpress.WebCore/WebUri/UriScheme.cs b/src/WebExpress.WebCore/WebUri/UriScheme.cs index 661a592..2b11f04 100644 --- a/src/WebExpress.WebCore/WebUri/UriScheme.cs +++ b/src/WebExpress.WebCore/WebUri/UriScheme.cs @@ -1,16 +1,69 @@ namespace WebExpress.WebCore.WebUri { /// - /// The typ of the uri. + /// The type of the URI. /// public enum UriScheme { + /// + /// The File URI scheme. + /// File, + + /// + /// The FTP URI scheme. + /// FTP, + + /// + /// The HTTP URI scheme. + /// Http, + + /// + /// The HTTPS URI scheme. + /// Https, + + /// + /// The LDAP URI scheme. + /// Ldap, + + /// + /// The LDAPS URI scheme. + /// Ldaps, + + /// + /// The Mailto URI scheme. + /// Mailto } + + /// + /// Extension methods for the enum. + /// + public static class UriSchemeExtension + { + /// + /// Converts the to its string representation. + /// + /// The URI scheme to convert. + /// The string representation of the URI scheme. + public static string ToString(this UriScheme scheme) + { + return scheme switch + { + UriScheme.File => "file", + UriScheme.FTP => "ftp", + UriScheme.Http => "http", + UriScheme.Https => "https", + UriScheme.Ldap => "ldap", + UriScheme.Ldaps => "ldaps", + UriScheme.Mailto => "mailto", + _ => "http" + }; + } + } } \ No newline at end of file