From fd6d71fce1d451b63c1302059a211449735f2b46 Mon Sep 17 00:00:00 2001 From: ReneSchwarzer Date: Wed, 1 Nov 2023 17:56:44 +0100 Subject: [PATCH 1/6] Initial commit --- README.md | 20 +- icon.png | Bin 0 -> 3335 bytes licenses/Net.lic | 23 + licenses/WebExpress.lic | 21 + src/WebExpress.Test/GlobalUsings.cs | 1 + .../Message/UnitTestGetRequest.cs | 92 +++ .../Message/UnitTestPostRequest.cs | 145 +++++ .../Message/UnitTestRequest.cs | 59 ++ src/WebExpress.Test/Request/Request.cs | 18 + src/WebExpress.Test/WebExpress.Test.csproj | 29 + .../test/contentTypeMultipartFormData1.post | 45 ++ .../test/contentTypeMultipartFormData2.post | Bin 0 -> 51014 bytes .../test/contentTypeMultipartFormData3.post | 23 + .../test/contentTypeMultipartFormData4.post | 83 +++ .../contentTypeMultipartFormData_Umlaut.post | 31 + .../test/contentTypeTextPlain.post | 31 + .../test/contentTypeTextPlain_Umlaut.post | 21 + .../test/contentTypeXwwwFormUrlencoded.post | 19 + .../contentTypeXwwwFormUrlencoded_umlaut.post | 19 + src/WebExpress.Test/test/general.get | 14 + src/WebExpress.Test/test/less.get | 2 + src/WebExpress.Test/test/massive.get | 113 ++++ src/WebExpress.Test/test/param.get | 14 + src/WebExpress.Test/test/param_umlaut.get | 14 + src/WebExpress.sln | 31 + src/WebExpress/ArguemtParserResult.cs | 12 + src/WebExpress/ArgumentParser.cs | 130 ++++ src/WebExpress/ArgumentParserCommand.cs | 18 + src/WebExpress/Config/EndpointConfig.cs | 36 ++ src/WebExpress/Config/HttpServerConfig.cs | 80 +++ src/WebExpress/Config/LimitConfig.cs | 30 + src/WebExpress/Config/PluginConfig.cs | 30 + src/WebExpress/HttpServer.cs | 581 +++++++++++++++++ src/WebExpress/HttpServerContext.cs | 111 ++++ src/WebExpress/IHost.cs | 20 + src/WebExpress/IHttpServerContext.cs | 68 ++ src/WebExpress/Internationalization/II18N.cs | 12 + .../InternationalizationDictionary.cs | 12 + .../InternationalizationExtensions.cs | 42 ++ .../InternationalizationItem.cs | 8 + .../InternationalizationManager.cs | 237 +++++++ src/WebExpress/Internationalization/de | 131 ++++ src/WebExpress/Internationalization/en | 130 ++++ src/WebExpress/Log.cs | 582 ++++++++++++++++++ src/WebExpress/LogFactory.cs | 36 ++ src/WebExpress/LogFrame.cs | 80 +++ src/WebExpress/LogFrameSimple.cs | 58 ++ src/WebExpress/LogItem.cs | 88 +++ src/WebExpress/Properties/launchSettings.json | 12 + src/WebExpress/Rocket.ico | Bin 0 -> 70141 bytes src/WebExpress/Setting/ISettingItem.cs | 6 + src/WebExpress/Setting/SettingLogItem.cs | 54 ++ .../WebApplication/ApplicationContext.cs | 70 +++ .../WebApplication/ApplicationDictionary.cs | 13 + .../WebApplication/ApplicationItem.cs | 25 + .../WebApplication/ApplicationManager.cs | 414 +++++++++++++ src/WebExpress/WebApplication/IApplication.cs | 21 + .../WebApplication/IApplicationContext.cs | 57 ++ .../WebAttribute/ApplicationAttribute.cs | 31 + .../WebAttribute/AssetPathAttribute.cs | 14 + src/WebExpress/WebAttribute/CacheAttribute.cs | 19 + .../WebAttribute/ConditionAttribute.cs | 20 + .../WebAttribute/ContextPathAttribute.cs | 17 + .../WebAttribute/DataPathAttribute.cs | 14 + .../WebAttribute/DefaultAttribute.cs | 19 + .../WebAttribute/DependencyAttribute.cs | 20 + .../WebAttribute/DescriptionAttribute.cs | 14 + .../WebAttribute/IApplicationAttribute.cs | 9 + .../WebAttribute/IModuleAttribute.cs | 9 + .../WebAttribute/IPluginAttribute.cs | 9 + .../WebAttribute/IResourceAttribute.cs | 9 + .../WebAttribute/ISegmentAttribute.cs | 13 + .../WebAttribute/IStatusPageAttribute.cs | 9 + src/WebExpress/WebAttribute/IconAttribute.cs | 14 + .../WebAttribute/IncludeSubPathsAttribute.cs | 17 + src/WebExpress/WebAttribute/JobAttribute.cs | 18 + .../WebAttribute/ModuleAttribute.cs | 21 + src/WebExpress/WebAttribute/NameAttribute.cs | 14 + .../WebAttribute/OptionAttribute.cs | 42 ++ .../WebAttribute/OptionalAttribute.cs | 19 + .../WebAttribute/ParentAttribute.cs | 17 + src/WebExpress/WebAttribute/ScopeAttribute.cs | 20 + .../WebAttribute/SegmentAttribute.cs | 42 ++ .../WebAttribute/SegmentDoubleAttribute.cs | 50 ++ .../WebAttribute/SegmentGuidAttribute.cs | 50 ++ .../WebAttribute/SegmentIntAttribute.cs | 38 ++ .../WebAttribute/SegmentStringAttribute.cs | 50 ++ .../WebAttribute/SegmentUIntAttribute.cs | 55 ++ .../WebAttribute/StatusCodeAttribute.cs | 17 + .../WebAttribute/SystemPluginAttribute.cs | 19 + src/WebExpress/WebAttribute/TitleAttribute.cs | 14 + .../WebComponent/ComponentDictionary.cs | 15 + src/WebExpress/WebComponent/ComponentItem.cs | 30 + .../WebComponent/ComponentManager.cs | 488 +++++++++++++++ src/WebExpress/WebComponent/IComponent.cs | 30 + .../WebComponent/IComponentPlugin.cs | 29 + .../WebComponent/IExecutableElements.cs | 28 + .../WebComponent/ISystemComponent.cs | 9 + src/WebExpress/WebCondition/ICondition.cs | 17 + src/WebExpress/WebEvent/EventDictionary.cs | 14 + src/WebExpress/WebEvent/EventManager.cs | 174 ++++++ src/WebExpress/WebEvent/IEventContext.cs | 18 + src/WebExpress/WebEvent/IEventHandler.cs | 6 + src/WebExpress/WebEx.cs | 275 +++++++++ src/WebExpress/WebExpress.csproj | 59 ++ src/WebExpress/WebHtml/Css.cs | 28 + src/WebExpress/WebHtml/Favicon.cs | 80 +++ .../WebHtml/HTMLElementExtension.cs | 122 ++++ src/WebExpress/WebHtml/HtmlAttribute.cs | 58 ++ .../WebHtml/HtmlAttributeNoneValue.cs | 43 ++ src/WebExpress/WebHtml/HtmlComment.cs | 41 ++ src/WebExpress/WebHtml/HtmlElement.cs | 400 ++++++++++++ src/WebExpress/WebHtml/HtmlElementEditDel.cs | 78 +++ src/WebExpress/WebHtml/HtmlElementEditIns.cs | 78 +++ .../WebHtml/HtmlElementEmbeddedEmbed.cs | 46 ++ .../WebHtml/HtmlElementEmbeddedIframe.cs | 44 ++ .../WebHtml/HtmlElementEmbeddedObject.cs | 46 ++ .../WebHtml/HtmlElementEmbeddedParam.cs | 18 + .../WebHtml/HtmlElementEmbeddedPicture.cs | 44 ++ .../WebHtml/HtmlElementEmbeddedSource.cs | 18 + .../WebHtml/HtmlElementFieldButton.cs | 134 ++++ .../WebHtml/HtmlElementFieldInput.cs | 211 +++++++ .../WebHtml/HtmlElementFieldLabel.cs | 70 +++ .../WebHtml/HtmlElementFieldLegend.cs | 37 ++ .../WebHtml/HtmlElementFieldSelect.cs | 94 +++ .../WebHtml/HtmlElementFormDatalist.cs | 54 ++ .../WebHtml/HtmlElementFormFieldset.cs | 73 +++ src/WebExpress/WebHtml/HtmlElementFormForm.cs | 109 ++++ .../WebHtml/HtmlElementFormKeygen.cs | 39 ++ .../WebHtml/HtmlElementFormMeter.cs | 102 +++ .../WebHtml/HtmlElementFormOptgroup.cs | 60 ++ .../WebHtml/HtmlElementFormOption.cs | 71 +++ .../WebHtml/HtmlElementFormOutput.cs | 39 ++ .../WebHtml/HtmlElementFormProgress.cs | 75 +++ .../WebHtml/HtmlElementFormTextarea.cs | 149 +++++ .../WebHtml/HtmlElementInteractiveCommand.cs | 44 ++ .../WebHtml/HtmlElementInteractiveDetails.cs | 44 ++ .../WebHtml/HtmlElementInteractiveMenu.cs | 44 ++ .../WebHtml/HtmlElementInteractiveSummary.cs | 44 ++ .../WebHtml/HtmlElementMetadataBase.cs | 46 ++ .../WebHtml/HtmlElementMetadataHead.cs | 216 +++++++ .../WebHtml/HtmlElementMetadataLink.cs | 43 ++ .../WebHtml/HtmlElementMetadataMeta.cs | 70 +++ .../WebHtml/HtmlElementMetadataStyle.cs | 59 ++ .../WebHtml/HtmlElementMetadataTitle.cs | 55 ++ .../WebHtml/HtmlElementMultimediaArea.cs | 17 + .../WebHtml/HtmlElementMultimediaAudio.cs | 26 + .../WebHtml/HtmlElementMultimediaImg.cs | 73 +++ .../WebHtml/HtmlElementMultimediaMap.cs | 17 + .../WebHtml/HtmlElementMultimediaMath.cs | 44 ++ .../WebHtml/HtmlElementMultimediaSvg.cs | 72 +++ .../WebHtml/HtmlElementMultimediaTrack.cs | 17 + .../WebHtml/HtmlElementMultimediaVideo.cs | 26 + src/WebExpress/WebHtml/HtmlElementRootHtml.cs | 64 ++ .../WebHtml/HtmlElementScriptingCanvas.cs | 37 ++ .../WebHtml/HtmlElementScriptingNoscript.cs | 44 ++ .../WebHtml/HtmlElementScriptingScript.cs | 82 +++ .../WebHtml/HtmlElementSectionAddress.cs | 44 ++ .../WebHtml/HtmlElementSectionArticle.cs | 44 ++ .../WebHtml/HtmlElementSectionAside.cs | 44 ++ .../WebHtml/HtmlElementSectionBody.cs | 92 +++ .../WebHtml/HtmlElementSectionFooter.cs | 64 ++ .../WebHtml/HtmlElementSectionH1.cs | 59 ++ .../WebHtml/HtmlElementSectionH2.cs | 59 ++ .../WebHtml/HtmlElementSectionH3.cs | 59 ++ .../WebHtml/HtmlElementSectionH4.cs | 59 ++ .../WebHtml/HtmlElementSectionH5.cs | 59 ++ .../WebHtml/HtmlElementSectionH6.cs | 59 ++ .../WebHtml/HtmlElementSectionHeader.cs | 44 ++ .../WebHtml/HtmlElementSectionMain.cs | 44 ++ .../WebHtml/HtmlElementSectionNav.cs | 43 ++ .../WebHtml/HtmlElementSectionSection.cs | 44 ++ .../WebHtml/HtmlElementTableCaption.cs | 59 ++ src/WebExpress/WebHtml/HtmlElementTableCol.cs | 17 + .../WebHtml/HtmlElementTableColgroup.cs | 17 + .../WebHtml/HtmlElementTableTable.cs | 49 ++ .../WebHtml/HtmlElementTableTbody.cs | 38 ++ src/WebExpress/WebHtml/HtmlElementTableTd.cs | 48 ++ .../WebHtml/HtmlElementTableTfoot.cs | 38 ++ src/WebExpress/WebHtml/HtmlElementTableTh.cs | 38 ++ .../WebHtml/HtmlElementTableThead.cs | 38 ++ src/WebExpress/WebHtml/HtmlElementTableTr.cs | 38 ++ .../HtmlElementTextContentBlockquote.cs | 44 ++ .../WebHtml/HtmlElementTextContentDd.cs | 44 ++ .../WebHtml/HtmlElementTextContentDiv.cs | 44 ++ .../WebHtml/HtmlElementTextContentDl.cs | 44 ++ .../WebHtml/HtmlElementTextContentDt.cs | 44 ++ .../HtmlElementTextContentFigcaption.cs | 44 ++ .../WebHtml/HtmlElementTextContentFigure.cs | 44 ++ .../WebHtml/HtmlElementTextContentHr.cs | 29 + .../WebHtml/HtmlElementTextContentLi.cs | 43 ++ .../WebHtml/HtmlElementTextContentOl.cs | 49 ++ .../WebHtml/HtmlElementTextContentP.cs | 59 ++ .../WebHtml/HtmlElementTextContentPre.cs | 44 ++ .../WebHtml/HtmlElementTextContentUl.cs | 44 ++ .../WebHtml/HtmlElementTextSemanticsA.cs | 101 +++ .../WebHtml/HtmlElementTextSemanticsAbbr.cs | 44 ++ .../WebHtml/HtmlElementTextSemanticsB.cs | 59 ++ .../WebHtml/HtmlElementTextSemanticsBdi.cs | 68 ++ .../WebHtml/HtmlElementTextSemanticsBdo.cs | 68 ++ .../WebHtml/HtmlElementTextSemanticsBr.cs | 16 + .../WebHtml/HtmlElementTextSemanticsCite.cs | 64 ++ .../WebHtml/HtmlElementTextSemanticsCode.cs | 44 ++ .../WebHtml/HtmlElementTextSemanticsData.cs | 54 ++ .../WebHtml/HtmlElementTextSemanticsDfn.cs | 44 ++ .../WebHtml/HtmlElementTextSemanticsEm.cs | 44 ++ .../WebHtml/HtmlElementTextSemanticsI.cs | 59 ++ .../WebHtml/HtmlElementTextSemanticsKdb.cs | 44 ++ .../WebHtml/HtmlElementTextSemanticsMark.cs | 59 ++ .../WebHtml/HtmlElementTextSemanticsQ.cs | 44 ++ .../WebHtml/HtmlElementTextSemanticsRp.cs | 59 ++ .../WebHtml/HtmlElementTextSemanticsRt.cs | 59 ++ .../WebHtml/HtmlElementTextSemanticsRuby.cs | 44 ++ .../WebHtml/HtmlElementTextSemanticsS.cs | 59 ++ .../WebHtml/HtmlElementTextSemanticsSamp.cs | 44 ++ .../WebHtml/HtmlElementTextSemanticsSmall.cs | 59 ++ .../WebHtml/HtmlElementTextSemanticsSpan.cs | 44 ++ .../WebHtml/HtmlElementTextSemanticsStrong.cs | 59 ++ .../WebHtml/HtmlElementTextSemanticsSub.cs | 44 ++ .../WebHtml/HtmlElementTextSemanticsSup.cs | 44 ++ .../WebHtml/HtmlElementTextSemanticsTime.cs | 49 ++ .../WebHtml/HtmlElementTextSemanticsU.cs | 59 ++ .../WebHtml/HtmlElementTextSemanticsVar.cs | 44 ++ .../WebHtml/HtmlElementTextSemanticsWbr.cs | 16 + .../WebHtml/HtmlElementWebComponentsSlot.cs | 44 ++ .../HtmlElementWebComponentsTemplate.cs | 44 ++ src/WebExpress/WebHtml/HtmlEmpty.cs | 30 + src/WebExpress/WebHtml/HtmlList.cs | 82 +++ src/WebExpress/WebHtml/HtmlNbsp.cs | 30 + src/WebExpress/WebHtml/HtmlRaw.cs | 48 ++ src/WebExpress/WebHtml/HtmlText.cs | 39 ++ src/WebExpress/WebHtml/IHtml.cs | 14 + src/WebExpress/WebHtml/IHtmlAttribute.cs | 11 + src/WebExpress/WebHtml/IHtmlElementEdit.cs | 9 + .../WebHtml/IHtmlElementEmbedded.cs | 9 + src/WebExpress/WebHtml/IHtmlElementForm.cs | 9 + .../WebHtml/IHtmlElementInteractive.cs | 9 + .../WebHtml/IHtmlElementMetadata.cs | 9 + .../WebHtml/IHtmlElementMultimedia.cs | 9 + src/WebExpress/WebHtml/IHtmlElementRoot.cs | 9 + .../WebHtml/IHtmlElementScripting.cs | 9 + src/WebExpress/WebHtml/IHtmlElementSection.cs | 9 + src/WebExpress/WebHtml/IHtmlElementTable.cs | 9 + .../WebHtml/IHtmlElementTextContent.cs | 9 + .../WebHtml/IHtmlElementTextSemantics.cs | 9 + .../WebHtml/IHtmlElementWebComponents.cs | 9 + src/WebExpress/WebHtml/IHtmlFormularItem.cs | 6 + src/WebExpress/WebHtml/IHtmlNode.cs | 6 + src/WebExpress/WebHtml/Style.cs | 28 + src/WebExpress/WebHtml/TypeEnctype.cs | 60 ++ src/WebExpress/WebHtml/TypeFavicon.cs | 11 + src/WebExpress/WebHtml/TypeTarget.cs | 33 + src/WebExpress/WebJob/Clock.cs | 198 ++++++ src/WebExpress/WebJob/Cron.cs | 155 +++++ src/WebExpress/WebJob/IJob.cs | 22 + src/WebExpress/WebJob/IJobContext.cs | 28 + src/WebExpress/WebJob/Job.cs | 38 ++ src/WebExpress/WebJob/JobContext.cs | 47 ++ src/WebExpress/WebJob/JobManager.cs | 431 +++++++++++++ src/WebExpress/WebJob/ScheduleDictionary.cs | 13 + src/WebExpress/WebJob/ScheduleIDynamicItem.cs | 25 + src/WebExpress/WebJob/ScheduleStaticItem.cs | 180 ++++++ .../WebJob/ScheduleStaticItemValue.cs | 25 + src/WebExpress/WebMessage/HttpContext.cs | 82 +++ .../WebMessage/HttpExceptionContext.cs | 33 + src/WebExpress/WebMessage/Parameter.cs | 137 +++++ .../WebMessage/ParameterDictionary.cs | 11 + src/WebExpress/WebMessage/ParameterFile.cs | 56 ++ src/WebExpress/WebMessage/ParameterScope.cs | 28 + src/WebExpress/WebMessage/Request.cs | 541 ++++++++++++++++ .../WebMessage/RequestAuthorization.cs | 61 ++ .../WebMessage/RequestHeaderFields.cs | 114 ++++ src/WebExpress/WebMessage/RequestMethod.cs | 35 ++ src/WebExpress/WebMessage/Response.cs | 35 ++ .../WebMessage/ResponseBadRequest.cs | 22 + .../WebMessage/ResponseForbidden.cs | 22 + .../WebMessage/ResponseHeaderFields.cs | 130 ++++ .../WebMessage/ResponseInternalServerError.cs | 22 + src/WebExpress/WebMessage/ResponseNotFound.cs | 22 + src/WebExpress/WebMessage/ResponseOK.cs | 17 + .../ResponseRedirectPermanentlyMoved.cs | 19 + .../ResponseRedirectTemporarilyMoved.cs | 22 + .../WebMessage/ResponseUnauthorized.cs | 19 + src/WebExpress/WebModule/IModule.cs | 18 + src/WebExpress/WebModule/IModuleContext.cs | 54 ++ src/WebExpress/WebModule/ModuleContext.cs | 70 +++ src/WebExpress/WebModule/ModuleDictionary.cs | 13 + src/WebExpress/WebModule/ModuleItem.cs | 283 +++++++++ .../WebModule/ModuleItemInstance.cs | 22 + src/WebExpress/WebModule/ModuleManager.cs | 446 ++++++++++++++ src/WebExpress/WebPackage/PackageBuilder.cs | 290 +++++++++ src/WebExpress/WebPackage/PackageCatalog.cs | 35 ++ .../WebPackage/PackageCatalogItem.cs | 49 ++ .../WebPackage/PackageCatalogeItemState.cs | 20 + src/WebExpress/WebPackage/PackageItem.cs | 69 +++ src/WebExpress/WebPackage/PackageItemSpec.cs | 89 +++ src/WebExpress/WebPackage/PackageManager.cs | 480 +++++++++++++++ src/WebExpress/WebPage/IPage.cs | 19 + src/WebExpress/WebPage/IVisualTree.cs | 74 +++ src/WebExpress/WebPage/Page.cs | 71 +++ src/WebExpress/WebPage/RenderContext.cs | 91 +++ src/WebExpress/WebPage/VisualTree.cs | 129 ++++ src/WebExpress/WebPlugin/IPlugin.cs | 21 + src/WebExpress/WebPlugin/IPluginContext.cs | 61 ++ src/WebExpress/WebPlugin/PluginContext.cs | 74 +++ src/WebExpress/WebPlugin/PluginDictionary.cs | 13 + src/WebExpress/WebPlugin/PluginItem.cs | 42 ++ src/WebExpress/WebPlugin/PluginLoadContext.cs | 61 ++ src/WebExpress/WebPlugin/PluginManager.cs | 582 ++++++++++++++++++ src/WebExpress/WebResource/IResource.cs | 57 ++ .../WebResource/IResourceContext.cs | 69 +++ .../WebResource/RedirectException.cs | 29 + src/WebExpress/WebResource/Resource.cs | 78 +++ src/WebExpress/WebResource/ResourceAsset.cs | 156 +++++ src/WebExpress/WebResource/ResourceBinary.cs | 38 ++ src/WebExpress/WebResource/ResourceContext.cs | 103 ++++ .../WebResource/ResourceDictionary.cs | 13 + src/WebExpress/WebResource/ResourceFile.cs | 126 ++++ src/WebExpress/WebResource/ResourceItem.cs | 226 +++++++ src/WebExpress/WebResource/ResourceManager.cs | 396 ++++++++++++ src/WebExpress/WebResource/ResourceRest.cs | 84 +++ src/WebExpress/WebScope/IScope.cs | 9 + .../WebSession/AuthorizationService.cs | 12 + src/WebExpress/WebSession/ISessionProperty.cs | 9 + src/WebExpress/WebSession/Session.cs | 126 ++++ .../WebSession/SessionDictionary.cs | 14 + src/WebExpress/WebSession/SessionManager.cs | 98 +++ src/WebExpress/WebSession/SessionProperty.cs | 9 + .../SessionPropertyAuthentification.cs | 15 + .../SessionPropertyAuthorization.cs | 6 + .../WebSession/SessionPropertyParameter.cs | 30 + src/WebExpress/WebSitemap/SearchContext.cs | 26 + src/WebExpress/WebSitemap/SearchResult.cs | 74 +++ src/WebExpress/WebSitemap/SitemapManager.cs | 514 ++++++++++++++++ src/WebExpress/WebSitemap/SitemapNode.cs | 178 ++++++ src/WebExpress/WebStatusPage/IStatusPage.cs | 62 ++ .../WebStatusPage/ResponseDictionary.cs | 13 + .../WebStatusPage/ResponseDictionaryItem.cs | 12 + src/WebExpress/WebStatusPage/ResponseItem.cs | 33 + .../WebStatusPage/ResponseManager.cs | 346 +++++++++++ src/WebExpress/WebTask/ITask.cs | 52 ++ src/WebExpress/WebTask/Task.cs | 118 ++++ src/WebExpress/WebTask/TaskDictionary.cs | 12 + src/WebExpress/WebTask/TaskEventArgs.cs | 8 + src/WebExpress/WebTask/TaskManager.cs | 155 +++++ src/WebExpress/WebTask/TaskState.cs | 22 + src/WebExpress/WebUri/IUriPathSegment.cs | 61 ++ .../WebUri/IUriPathSegmentConstant.cs | 10 + .../WebUri/IUriPathSegmentVariable.cs | 32 + src/WebExpress/WebUri/UriAuthority.cs | 90 +++ src/WebExpress/WebUri/UriFragment.cs | 26 + .../WebUri/UriPathSegmentConstant.cs | 122 ++++ src/WebExpress/WebUri/UriPathSegmentRoot.cs | 108 ++++ .../WebUri/UriPathSegmentVariable.cs | 146 +++++ .../WebUri/UriPathSegmentVariableDouble.cs | 79 +++ .../WebUri/UriPathSegmentVariableGuid.cs | 131 ++++ .../WebUri/UriPathSegmentVariableInt.cs | 79 +++ .../WebUri/UriPathSegmentVariableString.cs | 79 +++ .../WebUri/UriPathSegmentVariableUInt.cs | 79 +++ src/WebExpress/WebUri/UriQuerry.cs | 29 + src/WebExpress/WebUri/UriResource.cs | 532 ++++++++++++++++ src/WebExpress/WebUri/UriScheme.cs | 16 + 362 files changed, 24235 insertions(+), 1 deletion(-) create mode 100644 icon.png create mode 100644 licenses/Net.lic create mode 100644 licenses/WebExpress.lic create mode 100644 src/WebExpress.Test/GlobalUsings.cs create mode 100644 src/WebExpress.Test/Message/UnitTestGetRequest.cs create mode 100644 src/WebExpress.Test/Message/UnitTestPostRequest.cs create mode 100644 src/WebExpress.Test/Message/UnitTestRequest.cs create mode 100644 src/WebExpress.Test/Request/Request.cs create mode 100644 src/WebExpress.Test/WebExpress.Test.csproj create mode 100644 src/WebExpress.Test/test/contentTypeMultipartFormData1.post create mode 100644 src/WebExpress.Test/test/contentTypeMultipartFormData2.post create mode 100644 src/WebExpress.Test/test/contentTypeMultipartFormData3.post create mode 100644 src/WebExpress.Test/test/contentTypeMultipartFormData4.post create mode 100644 src/WebExpress.Test/test/contentTypeMultipartFormData_Umlaut.post create mode 100644 src/WebExpress.Test/test/contentTypeTextPlain.post create mode 100644 src/WebExpress.Test/test/contentTypeTextPlain_Umlaut.post create mode 100644 src/WebExpress.Test/test/contentTypeXwwwFormUrlencoded.post create mode 100644 src/WebExpress.Test/test/contentTypeXwwwFormUrlencoded_umlaut.post create mode 100644 src/WebExpress.Test/test/general.get create mode 100644 src/WebExpress.Test/test/less.get create mode 100644 src/WebExpress.Test/test/massive.get create mode 100644 src/WebExpress.Test/test/param.get create mode 100644 src/WebExpress.Test/test/param_umlaut.get create mode 100644 src/WebExpress.sln create mode 100644 src/WebExpress/ArguemtParserResult.cs create mode 100644 src/WebExpress/ArgumentParser.cs create mode 100644 src/WebExpress/ArgumentParserCommand.cs create mode 100644 src/WebExpress/Config/EndpointConfig.cs create mode 100644 src/WebExpress/Config/HttpServerConfig.cs create mode 100644 src/WebExpress/Config/LimitConfig.cs create mode 100644 src/WebExpress/Config/PluginConfig.cs create mode 100644 src/WebExpress/HttpServer.cs create mode 100644 src/WebExpress/HttpServerContext.cs create mode 100644 src/WebExpress/IHost.cs create mode 100644 src/WebExpress/IHttpServerContext.cs create mode 100644 src/WebExpress/Internationalization/II18N.cs create mode 100644 src/WebExpress/Internationalization/InternationalizationDictionary.cs create mode 100644 src/WebExpress/Internationalization/InternationalizationExtensions.cs create mode 100644 src/WebExpress/Internationalization/InternationalizationItem.cs create mode 100644 src/WebExpress/Internationalization/InternationalizationManager.cs create mode 100644 src/WebExpress/Internationalization/de create mode 100644 src/WebExpress/Internationalization/en create mode 100644 src/WebExpress/Log.cs create mode 100644 src/WebExpress/LogFactory.cs create mode 100644 src/WebExpress/LogFrame.cs create mode 100644 src/WebExpress/LogFrameSimple.cs create mode 100644 src/WebExpress/LogItem.cs create mode 100644 src/WebExpress/Properties/launchSettings.json create mode 100644 src/WebExpress/Rocket.ico create mode 100644 src/WebExpress/Setting/ISettingItem.cs create mode 100644 src/WebExpress/Setting/SettingLogItem.cs create mode 100644 src/WebExpress/WebApplication/ApplicationContext.cs create mode 100644 src/WebExpress/WebApplication/ApplicationDictionary.cs create mode 100644 src/WebExpress/WebApplication/ApplicationItem.cs create mode 100644 src/WebExpress/WebApplication/ApplicationManager.cs create mode 100644 src/WebExpress/WebApplication/IApplication.cs create mode 100644 src/WebExpress/WebApplication/IApplicationContext.cs create mode 100644 src/WebExpress/WebAttribute/ApplicationAttribute.cs create mode 100644 src/WebExpress/WebAttribute/AssetPathAttribute.cs create mode 100644 src/WebExpress/WebAttribute/CacheAttribute.cs create mode 100644 src/WebExpress/WebAttribute/ConditionAttribute.cs create mode 100644 src/WebExpress/WebAttribute/ContextPathAttribute.cs create mode 100644 src/WebExpress/WebAttribute/DataPathAttribute.cs create mode 100644 src/WebExpress/WebAttribute/DefaultAttribute.cs create mode 100644 src/WebExpress/WebAttribute/DependencyAttribute.cs create mode 100644 src/WebExpress/WebAttribute/DescriptionAttribute.cs create mode 100644 src/WebExpress/WebAttribute/IApplicationAttribute.cs create mode 100644 src/WebExpress/WebAttribute/IModuleAttribute.cs create mode 100644 src/WebExpress/WebAttribute/IPluginAttribute.cs create mode 100644 src/WebExpress/WebAttribute/IResourceAttribute.cs create mode 100644 src/WebExpress/WebAttribute/ISegmentAttribute.cs create mode 100644 src/WebExpress/WebAttribute/IStatusPageAttribute.cs create mode 100644 src/WebExpress/WebAttribute/IconAttribute.cs create mode 100644 src/WebExpress/WebAttribute/IncludeSubPathsAttribute.cs create mode 100644 src/WebExpress/WebAttribute/JobAttribute.cs create mode 100644 src/WebExpress/WebAttribute/ModuleAttribute.cs create mode 100644 src/WebExpress/WebAttribute/NameAttribute.cs create mode 100644 src/WebExpress/WebAttribute/OptionAttribute.cs create mode 100644 src/WebExpress/WebAttribute/OptionalAttribute.cs create mode 100644 src/WebExpress/WebAttribute/ParentAttribute.cs create mode 100644 src/WebExpress/WebAttribute/ScopeAttribute.cs create mode 100644 src/WebExpress/WebAttribute/SegmentAttribute.cs create mode 100644 src/WebExpress/WebAttribute/SegmentDoubleAttribute.cs create mode 100644 src/WebExpress/WebAttribute/SegmentGuidAttribute.cs create mode 100644 src/WebExpress/WebAttribute/SegmentIntAttribute.cs create mode 100644 src/WebExpress/WebAttribute/SegmentStringAttribute.cs create mode 100644 src/WebExpress/WebAttribute/SegmentUIntAttribute.cs create mode 100644 src/WebExpress/WebAttribute/StatusCodeAttribute.cs create mode 100644 src/WebExpress/WebAttribute/SystemPluginAttribute.cs create mode 100644 src/WebExpress/WebAttribute/TitleAttribute.cs create mode 100644 src/WebExpress/WebComponent/ComponentDictionary.cs create mode 100644 src/WebExpress/WebComponent/ComponentItem.cs create mode 100644 src/WebExpress/WebComponent/ComponentManager.cs create mode 100644 src/WebExpress/WebComponent/IComponent.cs create mode 100644 src/WebExpress/WebComponent/IComponentPlugin.cs create mode 100644 src/WebExpress/WebComponent/IExecutableElements.cs create mode 100644 src/WebExpress/WebComponent/ISystemComponent.cs create mode 100644 src/WebExpress/WebCondition/ICondition.cs create mode 100644 src/WebExpress/WebEvent/EventDictionary.cs create mode 100644 src/WebExpress/WebEvent/EventManager.cs create mode 100644 src/WebExpress/WebEvent/IEventContext.cs create mode 100644 src/WebExpress/WebEvent/IEventHandler.cs create mode 100644 src/WebExpress/WebEx.cs create mode 100644 src/WebExpress/WebExpress.csproj create mode 100644 src/WebExpress/WebHtml/Css.cs create mode 100644 src/WebExpress/WebHtml/Favicon.cs create mode 100644 src/WebExpress/WebHtml/HTMLElementExtension.cs create mode 100644 src/WebExpress/WebHtml/HtmlAttribute.cs create mode 100644 src/WebExpress/WebHtml/HtmlAttributeNoneValue.cs create mode 100644 src/WebExpress/WebHtml/HtmlComment.cs create mode 100644 src/WebExpress/WebHtml/HtmlElement.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementEditDel.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementEditIns.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementEmbeddedEmbed.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementEmbeddedIframe.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementEmbeddedObject.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementEmbeddedParam.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementEmbeddedPicture.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementEmbeddedSource.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementFieldButton.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementFieldInput.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementFieldLabel.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementFieldLegend.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementFieldSelect.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementFormDatalist.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementFormFieldset.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementFormForm.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementFormKeygen.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementFormMeter.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementFormOptgroup.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementFormOption.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementFormOutput.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementFormProgress.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementFormTextarea.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementInteractiveCommand.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementInteractiveDetails.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementInteractiveMenu.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementInteractiveSummary.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementMetadataBase.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementMetadataHead.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementMetadataLink.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementMetadataMeta.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementMetadataStyle.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementMetadataTitle.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementMultimediaArea.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementMultimediaAudio.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementMultimediaImg.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementMultimediaMap.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementMultimediaMath.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementMultimediaSvg.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementMultimediaTrack.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementMultimediaVideo.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementRootHtml.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementScriptingCanvas.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementScriptingNoscript.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementScriptingScript.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementSectionAddress.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementSectionArticle.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementSectionAside.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementSectionBody.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementSectionFooter.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementSectionH1.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementSectionH2.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementSectionH3.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementSectionH4.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementSectionH5.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementSectionH6.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementSectionHeader.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementSectionMain.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementSectionNav.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementSectionSection.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementTableCaption.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementTableCol.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementTableColgroup.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementTableTable.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementTableTbody.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementTableTd.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementTableTfoot.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementTableTh.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementTableThead.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementTableTr.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementTextContentBlockquote.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementTextContentDd.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementTextContentDiv.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementTextContentDl.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementTextContentDt.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementTextContentFigcaption.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementTextContentFigure.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementTextContentHr.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementTextContentLi.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementTextContentOl.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementTextContentP.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementTextContentPre.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementTextContentUl.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementTextSemanticsA.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementTextSemanticsAbbr.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementTextSemanticsB.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementTextSemanticsBdi.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementTextSemanticsBdo.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementTextSemanticsBr.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementTextSemanticsCite.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementTextSemanticsCode.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementTextSemanticsData.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementTextSemanticsDfn.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementTextSemanticsEm.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementTextSemanticsI.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementTextSemanticsKdb.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementTextSemanticsMark.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementTextSemanticsQ.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementTextSemanticsRp.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementTextSemanticsRt.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementTextSemanticsRuby.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementTextSemanticsS.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementTextSemanticsSamp.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementTextSemanticsSmall.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementTextSemanticsSpan.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementTextSemanticsStrong.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementTextSemanticsSub.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementTextSemanticsSup.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementTextSemanticsTime.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementTextSemanticsU.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementTextSemanticsVar.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementTextSemanticsWbr.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementWebComponentsSlot.cs create mode 100644 src/WebExpress/WebHtml/HtmlElementWebComponentsTemplate.cs create mode 100644 src/WebExpress/WebHtml/HtmlEmpty.cs create mode 100644 src/WebExpress/WebHtml/HtmlList.cs create mode 100644 src/WebExpress/WebHtml/HtmlNbsp.cs create mode 100644 src/WebExpress/WebHtml/HtmlRaw.cs create mode 100644 src/WebExpress/WebHtml/HtmlText.cs create mode 100644 src/WebExpress/WebHtml/IHtml.cs create mode 100644 src/WebExpress/WebHtml/IHtmlAttribute.cs create mode 100644 src/WebExpress/WebHtml/IHtmlElementEdit.cs create mode 100644 src/WebExpress/WebHtml/IHtmlElementEmbedded.cs create mode 100644 src/WebExpress/WebHtml/IHtmlElementForm.cs create mode 100644 src/WebExpress/WebHtml/IHtmlElementInteractive.cs create mode 100644 src/WebExpress/WebHtml/IHtmlElementMetadata.cs create mode 100644 src/WebExpress/WebHtml/IHtmlElementMultimedia.cs create mode 100644 src/WebExpress/WebHtml/IHtmlElementRoot.cs create mode 100644 src/WebExpress/WebHtml/IHtmlElementScripting.cs create mode 100644 src/WebExpress/WebHtml/IHtmlElementSection.cs create mode 100644 src/WebExpress/WebHtml/IHtmlElementTable.cs create mode 100644 src/WebExpress/WebHtml/IHtmlElementTextContent.cs create mode 100644 src/WebExpress/WebHtml/IHtmlElementTextSemantics.cs create mode 100644 src/WebExpress/WebHtml/IHtmlElementWebComponents.cs create mode 100644 src/WebExpress/WebHtml/IHtmlFormularItem.cs create mode 100644 src/WebExpress/WebHtml/IHtmlNode.cs create mode 100644 src/WebExpress/WebHtml/Style.cs create mode 100644 src/WebExpress/WebHtml/TypeEnctype.cs create mode 100644 src/WebExpress/WebHtml/TypeFavicon.cs create mode 100644 src/WebExpress/WebHtml/TypeTarget.cs create mode 100644 src/WebExpress/WebJob/Clock.cs create mode 100644 src/WebExpress/WebJob/Cron.cs create mode 100644 src/WebExpress/WebJob/IJob.cs create mode 100644 src/WebExpress/WebJob/IJobContext.cs create mode 100644 src/WebExpress/WebJob/Job.cs create mode 100644 src/WebExpress/WebJob/JobContext.cs create mode 100644 src/WebExpress/WebJob/JobManager.cs create mode 100644 src/WebExpress/WebJob/ScheduleDictionary.cs create mode 100644 src/WebExpress/WebJob/ScheduleIDynamicItem.cs create mode 100644 src/WebExpress/WebJob/ScheduleStaticItem.cs create mode 100644 src/WebExpress/WebJob/ScheduleStaticItemValue.cs create mode 100644 src/WebExpress/WebMessage/HttpContext.cs create mode 100644 src/WebExpress/WebMessage/HttpExceptionContext.cs create mode 100644 src/WebExpress/WebMessage/Parameter.cs create mode 100644 src/WebExpress/WebMessage/ParameterDictionary.cs create mode 100644 src/WebExpress/WebMessage/ParameterFile.cs create mode 100644 src/WebExpress/WebMessage/ParameterScope.cs create mode 100644 src/WebExpress/WebMessage/Request.cs create mode 100644 src/WebExpress/WebMessage/RequestAuthorization.cs create mode 100644 src/WebExpress/WebMessage/RequestHeaderFields.cs create mode 100644 src/WebExpress/WebMessage/RequestMethod.cs create mode 100644 src/WebExpress/WebMessage/Response.cs create mode 100644 src/WebExpress/WebMessage/ResponseBadRequest.cs create mode 100644 src/WebExpress/WebMessage/ResponseForbidden.cs create mode 100644 src/WebExpress/WebMessage/ResponseHeaderFields.cs create mode 100644 src/WebExpress/WebMessage/ResponseInternalServerError.cs create mode 100644 src/WebExpress/WebMessage/ResponseNotFound.cs create mode 100644 src/WebExpress/WebMessage/ResponseOK.cs create mode 100644 src/WebExpress/WebMessage/ResponseRedirectPermanentlyMoved.cs create mode 100644 src/WebExpress/WebMessage/ResponseRedirectTemporarilyMoved.cs create mode 100644 src/WebExpress/WebMessage/ResponseUnauthorized.cs create mode 100644 src/WebExpress/WebModule/IModule.cs create mode 100644 src/WebExpress/WebModule/IModuleContext.cs create mode 100644 src/WebExpress/WebModule/ModuleContext.cs create mode 100644 src/WebExpress/WebModule/ModuleDictionary.cs create mode 100644 src/WebExpress/WebModule/ModuleItem.cs create mode 100644 src/WebExpress/WebModule/ModuleItemInstance.cs create mode 100644 src/WebExpress/WebModule/ModuleManager.cs create mode 100644 src/WebExpress/WebPackage/PackageBuilder.cs create mode 100644 src/WebExpress/WebPackage/PackageCatalog.cs create mode 100644 src/WebExpress/WebPackage/PackageCatalogItem.cs create mode 100644 src/WebExpress/WebPackage/PackageCatalogeItemState.cs create mode 100644 src/WebExpress/WebPackage/PackageItem.cs create mode 100644 src/WebExpress/WebPackage/PackageItemSpec.cs create mode 100644 src/WebExpress/WebPackage/PackageManager.cs create mode 100644 src/WebExpress/WebPage/IPage.cs create mode 100644 src/WebExpress/WebPage/IVisualTree.cs create mode 100644 src/WebExpress/WebPage/Page.cs create mode 100644 src/WebExpress/WebPage/RenderContext.cs create mode 100644 src/WebExpress/WebPage/VisualTree.cs create mode 100644 src/WebExpress/WebPlugin/IPlugin.cs create mode 100644 src/WebExpress/WebPlugin/IPluginContext.cs create mode 100644 src/WebExpress/WebPlugin/PluginContext.cs create mode 100644 src/WebExpress/WebPlugin/PluginDictionary.cs create mode 100644 src/WebExpress/WebPlugin/PluginItem.cs create mode 100644 src/WebExpress/WebPlugin/PluginLoadContext.cs create mode 100644 src/WebExpress/WebPlugin/PluginManager.cs create mode 100644 src/WebExpress/WebResource/IResource.cs create mode 100644 src/WebExpress/WebResource/IResourceContext.cs create mode 100644 src/WebExpress/WebResource/RedirectException.cs create mode 100644 src/WebExpress/WebResource/Resource.cs create mode 100644 src/WebExpress/WebResource/ResourceAsset.cs create mode 100644 src/WebExpress/WebResource/ResourceBinary.cs create mode 100644 src/WebExpress/WebResource/ResourceContext.cs create mode 100644 src/WebExpress/WebResource/ResourceDictionary.cs create mode 100644 src/WebExpress/WebResource/ResourceFile.cs create mode 100644 src/WebExpress/WebResource/ResourceItem.cs create mode 100644 src/WebExpress/WebResource/ResourceManager.cs create mode 100644 src/WebExpress/WebResource/ResourceRest.cs create mode 100644 src/WebExpress/WebScope/IScope.cs create mode 100644 src/WebExpress/WebSession/AuthorizationService.cs create mode 100644 src/WebExpress/WebSession/ISessionProperty.cs create mode 100644 src/WebExpress/WebSession/Session.cs create mode 100644 src/WebExpress/WebSession/SessionDictionary.cs create mode 100644 src/WebExpress/WebSession/SessionManager.cs create mode 100644 src/WebExpress/WebSession/SessionProperty.cs create mode 100644 src/WebExpress/WebSession/SessionPropertyAuthentification.cs create mode 100644 src/WebExpress/WebSession/SessionPropertyAuthorization.cs create mode 100644 src/WebExpress/WebSession/SessionPropertyParameter.cs create mode 100644 src/WebExpress/WebSitemap/SearchContext.cs create mode 100644 src/WebExpress/WebSitemap/SearchResult.cs create mode 100644 src/WebExpress/WebSitemap/SitemapManager.cs create mode 100644 src/WebExpress/WebSitemap/SitemapNode.cs create mode 100644 src/WebExpress/WebStatusPage/IStatusPage.cs create mode 100644 src/WebExpress/WebStatusPage/ResponseDictionary.cs create mode 100644 src/WebExpress/WebStatusPage/ResponseDictionaryItem.cs create mode 100644 src/WebExpress/WebStatusPage/ResponseItem.cs create mode 100644 src/WebExpress/WebStatusPage/ResponseManager.cs create mode 100644 src/WebExpress/WebTask/ITask.cs create mode 100644 src/WebExpress/WebTask/Task.cs create mode 100644 src/WebExpress/WebTask/TaskDictionary.cs create mode 100644 src/WebExpress/WebTask/TaskEventArgs.cs create mode 100644 src/WebExpress/WebTask/TaskManager.cs create mode 100644 src/WebExpress/WebTask/TaskState.cs create mode 100644 src/WebExpress/WebUri/IUriPathSegment.cs create mode 100644 src/WebExpress/WebUri/IUriPathSegmentConstant.cs create mode 100644 src/WebExpress/WebUri/IUriPathSegmentVariable.cs create mode 100644 src/WebExpress/WebUri/UriAuthority.cs create mode 100644 src/WebExpress/WebUri/UriFragment.cs create mode 100644 src/WebExpress/WebUri/UriPathSegmentConstant.cs create mode 100644 src/WebExpress/WebUri/UriPathSegmentRoot.cs create mode 100644 src/WebExpress/WebUri/UriPathSegmentVariable.cs create mode 100644 src/WebExpress/WebUri/UriPathSegmentVariableDouble.cs create mode 100644 src/WebExpress/WebUri/UriPathSegmentVariableGuid.cs create mode 100644 src/WebExpress/WebUri/UriPathSegmentVariableInt.cs create mode 100644 src/WebExpress/WebUri/UriPathSegmentVariableString.cs create mode 100644 src/WebExpress/WebUri/UriPathSegmentVariableUInt.cs create mode 100644 src/WebExpress/WebUri/UriQuerry.cs create mode 100644 src/WebExpress/WebUri/UriResource.cs create mode 100644 src/WebExpress/WebUri/UriScheme.cs diff --git a/README.md b/README.md index 4ef695f..83e5a5b 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,20 @@ +![WebExpress logo](https://raw.githubusercontent.com/ReneSchwarzer/WebExpress/icon.png) + # WebExpress -A lightweight web server for use in .net applications. +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: + +- 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. + +# Libraries used +- https://github.com/dotnet/core (MIT) + +# Establish +see https://github.com/ReneSchwarzer/WebExpress.Doc/blob/main/doc/installation_guide.md and https://github.com/ReneSchwarzer/WebExpress.Doc/blob/main/doc/development_guide.md + +#Tags +#Raspberry #Raspbian #IoT #NETCore #WebExpress \ No newline at end of file diff --git a/icon.png b/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..7ce92702956911b7495e8d6c25325608de09776c GIT binary patch literal 3335 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 literal 0 HcmV?d00001 diff --git a/licenses/Net.lic b/licenses/Net.lic new file mode 100644 index 0000000..8169891 --- /dev/null +++ b/licenses/Net.lic @@ -0,0 +1,23 @@ +The MIT License (MIT) + +Copyright(c).NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/licenses/WebExpress.lic b/licenses/WebExpress.lic new file mode 100644 index 0000000..39c453e --- /dev/null +++ b/licenses/WebExpress.lic @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2021 René Schwarzer + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/src/WebExpress.Test/GlobalUsings.cs b/src/WebExpress.Test/GlobalUsings.cs new file mode 100644 index 0000000..8c927eb --- /dev/null +++ b/src/WebExpress.Test/GlobalUsings.cs @@ -0,0 +1 @@ +global using Xunit; \ No newline at end of file diff --git a/src/WebExpress.Test/Message/UnitTestGetRequest.cs b/src/WebExpress.Test/Message/UnitTestGetRequest.cs new file mode 100644 index 0000000..e688332 --- /dev/null +++ b/src/WebExpress.Test/Message/UnitTestGetRequest.cs @@ -0,0 +1,92 @@ +using System.IO; +using System.Net.Http; +using Xunit; + +namespace WebExpress.Test.Message +{ + public class UnitTestGetRequest + { + [Fact] + public void Get_General() + { + var client = new HttpClient(); + //client. + + //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" + //); + } + + [Fact] + public void Get_Less() + { + //using var reader = new BinaryReader(new FileStream(Path.Combine("test", "less.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_Less" + //); + } + + [Fact] + public void Get_Massive() + { + //using var reader = new BinaryReader(new FileStream(Path.Combine("test", "massive.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_Massive" + //); + } + + [Fact] + public void Get_Param() + { + //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; + + //Assert.True + //( + // request.Uri?.ToString() == "/abc/xyz/A7BCCCA9-4C7E-4117-9EE2-ECC3381B605A" && + // param != null && param == "1", + // "Fehler in der Funktion Get_Param" + //); + } + + [Fact] + public void Get_Param_Umlaut() + { + //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; + + //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" + //); + } + } +} diff --git a/src/WebExpress.Test/Message/UnitTestPostRequest.cs b/src/WebExpress.Test/Message/UnitTestPostRequest.cs new file mode 100644 index 0000000..f61b203 --- /dev/null +++ b/src/WebExpress.Test/Message/UnitTestPostRequest.cs @@ -0,0 +1,145 @@ +namespace WebExpress.Test.Message +{ + public class UnitTestPostRequest : UnitTestRequest + { + //[Fact] + //public void Post_TextPlain() + //{ + // using var reader = new BinaryReader(new FileStream(Path.Combine("test", "contentTypeTextPlain.post"), FileMode.Open)); + // var request = Request.Create(reader, "127.0.0.1"); + // var param = request?.GetParameter("submit_manufactor")?.Value; + + // Assert.True + // ( + // param != null && param == "1" + // ); + //} + + //[Fact] + //public void Post_TextPlain_Umlaut() + //{ + // using var reader = new BinaryReader(new FileStream(Path.Combine("test", "contentTypeTextPlain_Umlaut.post"), FileMode.Open)); + // var request = Request.Create(reader, "127.0.0.1"); + // var a = request?.GetParameter("a")?.Value; + // var b = request?.GetParameter("b")?.Value; + // var s = request?.GetParameter("submit_")?.Value; + + // Assert.True + // ( + // a != null && a == "ä" && + // b != null && b == "ö ü" && + // s != null && s == "1" + // ); + //} + + //[Fact] + //public void Post_Urlencoded() + //{ + // using var reader = new BinaryReader(new FileStream(Path.Combine("test", "contentTypeXwwwFormUrlencoded.post"), FileMode.Open)); + // var request = Request.Create(reader, "127.0.0.1"); + // var param = request?.GetParameter("submit_manufactor")?.Value; + + // Assert.True + // ( + // param != null && param == "1" + // ); + //} + + //[Fact] + //public void Post_Urlencoded_Umlaut() + //{ + // using var reader = new BinaryReader(new FileStream(Path.Combine("test", "contentTypeXwwwFormUrlencoded_Umlaut.post"), FileMode.Open)); + // var request = Request.Create(reader, "127.0.0.1"); + // var a = request?.GetParameter("a")?.Value; + // var b = request?.GetParameter("b")?.Value; + // var s = request?.GetParameter("submit_")?.Value; + + // Assert.True + // ( + // a != null && a == "ä" && + // b != null && b == "ö ü" && + // s != null && s == "1", + // "Fehler in der Funktion Post_Urlencoded_Umlaut" + // ); + //} + + //[Fact] + //public void Post_Multipart1() + //{ + // using var reader = new BinaryReader(new FileStream(Path.Combine("test", "contentTypeMultipartFormData1.post"), FileMode.Open)); + // var request = Request.Create(reader, "127.0.0.1"); + // var param = request?.GetParameter("submit_manufactor")?.Value; + + // Assert.True + // ( + // param != null && param == "1" + // ); + //} + + //[Fact] + //public void Post_Multipart2() + //{ + // using var reader = new BinaryReader(new FileStream(Path.Combine("test", "contentTypeMultipartFormData2.post"), FileMode.Open)); + // var request = Request.Create(reader, "127.0.0.1"); + // var param = request?.GetParameter("submit_manufactor")?.Value; + + // var file = request?.GetParameter("image") as ParameterFile; + + // var temp = Path.Combine(Path.GetTempPath(), file.Value); + // File.WriteAllBytes(temp, file.Data); + + // Assert.True + // ( + // file.Data.Length == 47788 && + // param != null && param == "1" + // ); + //} + + //[Fact] + //public void Post_Multipart3() + //{ + // using var reader = new BinaryReader(new FileStream(Path.Combine("test", "contentTypeMultipartFormData3.post"), FileMode.Open)); + // var request = Request.Create(reader, "127.0.0.1"); + // var param = request?.GetParameter("submit_del")?.Value; + + // Assert.True + // ( + // param != null && param == "1" + // ); + //} + + //[Fact] + //public void Post_Multipart4() + //{ + // using var reader = new BinaryReader(new FileStream(Path.Combine("test", "contentTypeMultipartFormData4.post"), FileMode.Open)); + // var contextFeatures = Prepare(reader, RequestMethod.POST, ""); + // var request = new WebMessage.Request(contextFeatures, null, null); + // var param = request?.GetParameter("submit-formular-inventory")?.Value; + + // Assert.True + // ( + // param != null && param == "1" + // ); + //} + + //[Fact] + //public void Post_Multipart_Umlaut() + //{ + // using var reader = new BinaryReader(new FileStream(Path.Combine("test", "contentTypeMultipartFormData_Umlaut.post"), FileMode.Open)); + // var request = Request.Create(reader, "127.0.0.1"); + // var a = request?.GetParameter("a")?.Value; + // var b = request?.GetParameter("b")?.Value; + // var s = request?.GetParameter("submit_")?.Value; + + // Assert.True + // ( + // a != null && a == "ä" && + // b != null && b == "ö ü" && + // s != null && s == "1", + // "Fehler in der Funktion Post_Multipart_Umlaut" + // ); + //} + + + } +} diff --git a/src/WebExpress.Test/Message/UnitTestRequest.cs b/src/WebExpress.Test/Message/UnitTestRequest.cs new file mode 100644 index 0000000..b073b04 --- /dev/null +++ b/src/WebExpress.Test/Message/UnitTestRequest.cs @@ -0,0 +1,59 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Features; +using Microsoft.AspNetCore.Session; +using System.IO; +using System.Net; +using WebExpress.WebMessage; + +namespace WebExpress.Test.Message +{ + public class UnitTestRequest + { + /// + /// Erstellt die FeatureCollection + /// + /// Die Daten + /// Die Anfragemethode + /// Der Querystring + /// FeatureCollection + protected static IFeatureCollection Prepare(BinaryReader reader, RequestMethod method, string queryString) + { + var contextFeatures = new FeatureCollection(); + contextFeatures.Set(new HttpConnectionFeature() + { + LocalPort = 80, + LocalIpAddress = IPAddress.Loopback, + RemoteIpAddress = IPAddress.Loopback, + ConnectionId = "43D885B6-DB4D-4EDF-9908-B122A5FFC829" + }); + contextFeatures.Set(new HttpRequestFeature() + { + Path = "/", + Protocol = "HTTP/1.1", + Method = method.ToString(), + QueryString = queryString, + Headers = new HeaderDictionary() + }); + contextFeatures.Set(new HttpRequestIdentifierFeature() { TraceIdentifier = "16FC666F-D7DC-47FF-9FB8-9D0F8DFCEF99" }); + contextFeatures.Set(new SessionFeature() { }); + + var requestFeature = contextFeatures.Get(); + requestFeature.Headers.Host = "localhost"; + requestFeature.Headers.Connection = "keep-alive"; + requestFeature.Headers.ContentType = ""; + requestFeature.Headers.ContentLength = 0; + requestFeature.Headers.ContentLanguage = ""; + requestFeature.Headers.ContentEncoding = "utf-8"; + requestFeature.Headers.Accept = "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9"; + requestFeature.Headers.AcceptEncoding = "gzip, deflate, br"; + requestFeature.Headers.AcceptLanguage = "de,en;q=0.9,en-GB;q=0.8,de-DE;q=0.7,en-US;q=0.6"; + requestFeature.Headers.UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.80 Safari/537.36 Edg/98.0.1108.50"; + + //requestFeature.Headers.Cookie; + + requestFeature.Headers.Authorization = ""; + + return contextFeatures; + } + } +} diff --git a/src/WebExpress.Test/Request/Request.cs b/src/WebExpress.Test/Request/Request.cs new file mode 100644 index 0000000..8e7f3c5 --- /dev/null +++ b/src/WebExpress.Test/Request/Request.cs @@ -0,0 +1,18 @@ +using Xunit; + +namespace WebExpress.Test.Request +{ + + public class Request + { + + [Fact] + public void Get_1() + { + Assert.True + ( + true + ); + } + } +} diff --git a/src/WebExpress.Test/WebExpress.Test.csproj b/src/WebExpress.Test/WebExpress.Test.csproj new file mode 100644 index 0000000..a4bc854 --- /dev/null +++ b/src/WebExpress.Test/WebExpress.Test.csproj @@ -0,0 +1,29 @@ + + + + net7.0 + enable + enable + + false + true + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + diff --git a/src/WebExpress.Test/test/contentTypeMultipartFormData1.post b/src/WebExpress.Test/test/contentTypeMultipartFormData1.post new file mode 100644 index 0000000..2bb547e --- /dev/null +++ b/src/WebExpress.Test/test/contentTypeMultipartFormData1.post @@ -0,0 +1,45 @@ +POST /ix/manufactors/67d35a0f-7e94-4bfd-a309-36e9162a67ff HTTP/1.1 +Host: localhost +Connection: keep-alive +Content-Length: 747 +Cache-Control: max-age=0 +Upgrade-Insecure-Requests: 1 +User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36 Edg/87.0.664.60 +Origin: null +Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryjFS12lhPL99b0oIO +Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 +Sec-Fetch-Site: cross-site +Sec-Fetch-Mode: navigate +Sec-Fetch-User: ?1 +Sec-Fetch-Dest: document +Referer: http://localhost/ix/manufactors/67d35a0f-7e94-4bfd-a309-36e9162a67ff +Accept-Encoding: gzip, deflate, br +Accept-Language: de,en;q=0.9,en-GB;q=0.8,de-DE;q=0.7,en-US;q=0.6 + +------WebKitFormBoundaryjFS12lhPL99b0oIO +Content-Disposition: form-data; name="name" + +ROBOTRON +------WebKitFormBoundaryjFS12lhPL99b0oIO +Content-Disposition: form-data; name="description" + +VEB Kombinat Robotron +------WebKitFormBoundaryjFS12lhPL99b0oIO +Content-Disposition: form-data; name="files"; filename="" +Content-Type: application/octet-stream + + +------WebKitFormBoundaryjFS12lhPL99b0oIO +Content-Disposition: form-data; name="image"; filename="" +Content-Type: application/octet-stream + + +------WebKitFormBoundaryjFS12lhPL99b0oIO +Content-Disposition: form-data; name="tag" + + +------WebKitFormBoundaryjFS12lhPL99b0oIO +Content-Disposition: form-data; name="submit_manufactor" + +1 +------WebKitFormBoundaryjFS12lhPL99b0oIO-- diff --git a/src/WebExpress.Test/test/contentTypeMultipartFormData2.post b/src/WebExpress.Test/test/contentTypeMultipartFormData2.post new file mode 100644 index 0000000000000000000000000000000000000000..9064a229215020a4ef11486461b17194ed7e7e6b GIT binary patch literal 51014 zcmeFX2UJsEw=aqn1r-$q0fA5i=_0)gNEeXa!2kh5Nq~gV!2+W6j(~tlub~A9)kYVh zl+b$#HKB(ZZutMd@0@e*JLio%-Z-y~@nwu;udKEAnsd$hTeGY^pX(YJ0))UmLT*5a zmjloa4)ufyiA&gvhyfot2uOgWLQArO!q91Mj>16)8LcLAU)7y8pF;Q|i(9Q`YKz<1B33ZhQxB-0xfQ}%!N7t#0+#NlE_8B=-KB+u{$~dMB4p_64kAzJOYqe8fe00E+JJuAslC7ZMYZ5EKyy zJk(M*)PBYfa0R=70BRsR7bp)v*~t^?1`?8#5PT#k`dIRjprjS&Ia`sxr@2mL0J0NM z1;Oo{1Ps7%vaDf1H;@4IFU9=FL2al#c@P3bfE|HwG7|p%u`{ts0~E;W{P(`6WED#T z?4fpEZe(#^r_u*GfIQD``(HKve+9Vzl$?MH#13i?hB!(C9R0xV`~Z89gDaUiet@m# zzuohUykIY~q{!TW_(70=2oD4iP*eJcpvdTas`6J`f_#I3k-=YW@#|FAsm_q~zlW_e z?4NCnr(l>n6bAl#=lln-Lda5;V?X<4Kbu%zS4r1UUsvb9pM*UKX6FfZKV#r8O3$F4 zAU6Qm9p>dm)`u(96F}w;K;{F^53qxhcO@JI_ab)!?aBApk%b8Wxq{)I`~Vop9$*g! zx&a_ih?ko$00j1QgW8k9>gGGo$9|9-J+j? z>c886{}pqw%K#j}uAsj?x)H>d+=4*hg5>Cn>~Q}X2mBqk{vD%{N6wge|6E7yIu#=o z1qH=*4fUt`6ciV56cp#Pube0MY_KmsAYabG^wpFo%6jiFlmA?FRMb|aps0wUJ_cSQ z|G(<4ZU&>Exbfrcd#)W?U{66&uc+}<(a^_wZQ|;SI}0cB8yO=qBjc1BSFW;MRC@WO zPDoMUH9bqITGIwZ?q;>|I>y|EX6z8Du2+IR`e|NN;6uUQ73X>7;`tDfkk}_mcz;6b zsmykB!-%#xQEVhoK5xw1kvqMo8^4(9-9nK0_%NK8%n1d>^GBH{3V*d8-qn{mYrO1Z zLtQv)JY@d=Zbt)qlH0-dT#LolK7rI&_|c8QDL38Pa_cx*A94NgCy6E}c;`V(aCMg3 z692MIeg;8$=TCgQ&G_@SGIG#nGJSBFf@1m=txB92<;C~;7^rPU*Bx$tBHdmML}8a7&z4x;2*|sb6kx-nc>G&o(5zrIqR6mOq!@#Qce} z2>u{gLfd{l8r3XucxA3J$ZwJf{|vh#?0>xTL8ZmI{!wy`%&QPIach>2KhQbo+Qjr= z@nd3XpY{>A?7|81H|<2=@H$-O;4RL^vb_>KM7rDPBd;vm+Dkm%9rL?fgPMpy=2s7} z|aFkpD3sF>+A)UiLcI6=S=P7NRZWbOslbeVR3qelGT4@_sx#x$YG1Z z?(|XE^4;2as7ZVv%kdG-1J^fCN0%l&Nnr|c%;tgF=|jEiq^AdtrM;g`8u#Eb$I=I8 z0=29`O$R#jNAM{P%7Dpq;*Z#lh6+};sbCn?$0mtoyK5J>oOI()*x^7{vw$6w`A$+T z0P4H?`2<W>P2%J!Hjl|ixDCZNR zc9oU31O3KNNhiM65`WsqvF9S`d9uVL# z5uTd&=BXCKg?qZW>GJ0J&MdNcs>82KCfyh+&0MKCx5?34#2vEi`hku~=bGIwS=vqXLidGb9V%t>h*2Mx2r@juwfc8x;Bz0iu~;qJAI zoMmWs&!LW2{@+84m!lR-`K$~CWJvD?-lockIZrB0GGUXH6~k+YkBb<@Wbl%Ngo&&uf-3W%q^4)NzDsM46A8<^t92b{EX>}@Z(dkAXDS2q8<8vxUQHc&ow&H%f;xV& zv}1!-u$#~SmSue6*X%qO(ZiF<`NFsPap1hJw(Q1R$H6Ef0p(n=5=9KaO_w!48TD76 z+-u?PsmN<+O>*;_JD^V~(e=?Jsc!8|p0fBmYd0pzMx;e#J3WYR_;r!4vj|qa_vjvW>grV?hoGrIBPIm{HGzXP)l^mQ`<8zNZ_+=`RswRpWz@ z&tl{`PnH`1`4K{~do22VsS*`o^fH95XGUPNZM`qYNJ3ft=iF^p4F$dWQ#^v6g5n9q zy$&jdc=7pH5^3JSwT`oWd<}c_7QDIpRidE-L-ntkc^T?AX`8wN#%{~RM03Pn%a6y8 z%W1A#t^KhsRT|6bJHPzm<=f@_S#jU-mXEd?&N;->%$3Ym+NJAmCrc-+jdePM_l?Gc zH;uuK<970b2Vxf2n6w6Yb_()(6hSfdSGE=y)7)@C5&R)lp>>DcqbVJe*x=VTS_d&f zL-8lHne_uFGJcT}2|~%`7KX*NJuT7cJN_UYr@;Lg;Rk@Z%vJ_o;_-{T2}cKRpQQBe zdE95qr}NwICyx1(f|2j7e+)Kf{jdsnRZWaF-wdGGf(R>*u24#y5NuBWFuAcjBOB?L z_AggQ7e*=QzT+9Vjhyy%?#ahK-1|~}=RM)Phjjc3hguDhwn>YBzH+%N+-%KDH7LOad^FQVkjn$V>W}OH_@-HAu;#5CpZcN zE}4UD`CQA)QmWo9H(S_Y;@yj4nfuA?rLtYopJhCe#IjUG>KA|`9;?<` z1Ki|tGUITn?dl5zH^a`e*zEJCfl2JNjY~BWi)%9S5*c{n|iyLUT2-e4+h` zS0P`{gPF15^Xlz(eF`390j%dWI94Yk*fdduwWYE-M+cnx$;g-5iJTy(3dU?Tp@YMw zfaA0YpI6n}v3{=w?zQX`wld^0DXi@M;UME=im7A3y1wur25ypvN0q5VG>X3M8flJA z>L%v*tOsL^{3e%eZ!ZVQR*@(@y`CaV-zail@snSR!6u)o9bJ3=J z;iDeG+IiPs17nYcul7>sG=*_u?r->P!5OWtsFQ~BxyPJ?UY{c`k6!1F3U>5={LgU!(ERsX42DTCbOTL=C@kk?DQK&$Y~~PsB)qc ztiS~N&D_DJ3QC4KuazEUR^Ne}jy~h_gLi402>G-*P$8@G0V4>0qGGb;%8^Ok&i$)D zT@%#83|N|QbH+65@~$zYL}N|8!1+NYH?Pp47A1>QM5)i|SRCt+rLWM`ogw2CKiO+B zgf0A&T4Sp}D?HlO`?aw?yZJ*c4MQU6H`eS5fwQk35R&}&G@y-7t`L>ugFW_KG~F=y z@M2umk%W<(Po@sV21zZEkYB2Hg@WQ{`Q^udCkIa)AA-gIP9gSR58NPQlHwCwef{i8 zp&9o7YzH}+DY>dSZ%OpVWrNnJHaphqkJ>Btre}N>gn^e_w2~F_EberVp0W0uE%G19 zO-5hasESJ=Z0_`DhVN{&xlu(UkJwW&@I!Jyk^BVeDRMU+Xkag5xLh( zgRoHi;DlJXc#=3)a4lf0LNdlmj{%Lz{Nn)M?tFl~D=l#hfbiUbtr#tA<&``#YEvf_ zQ6H4lSxfWQNN*#qGTnGDb+;&BNLS;U@dH)&h{}EH{B2=Bok^^(tyS|JTH>czG>g2# zn38qJ_vEJU(Xzrw%_QZZaLv2fCZGrI)Q(%*Tm{O)mS>As(L9_pBUlpaPi7asXUqH) zE9=mS{ZP4B;6vbTZw;=+8S*t}wy&60C5ch8U>qyPf33;#^!*wVWW!x_5k}csUvjA_ zR3D$YHDV;2A{H-pH{aM47rn+i*ti@Z!Qme|SK2a`UvfLhUwwRus{g4@>B^6$?@`^% z5jNiQ=_75BB5z2*6k^k$*9H3?d8Yv3cbqt@I@oKwS-bkNDy!zyEupHnYI}^k<+q@} zQS@5iD|ol(mJ&knK=HUdQU&p4ylB)EAYVT!wNP6_6XQ*qIdeu&g$tVoHpf~dS>-10 zYnt;3(n62Z1FgWPm%THo#VQ>f??7)`>pcg|i1an70Wt3njIpi;pEs0?OVG$qZ>EZl zL+TR5K-qQpXlo*Ri#1fNM8kauv%K+xlC>mkE#g%0xM$8T7K>n^7xeGV?C+Ttq;njj zKOI4H)*f8JN+|APFXNh!QsFWIvr-|A+I@jFr%J2mDtwAh+HfH)Nj3hbHwL3cKIo9` z_HVJSE5;7(d`PMc#sgZznC6uw$hDQ|lEUN0n^^pZkY!ic_R$@aZS_37}u-2P9*hu^t=@uccj1Sdy1N>)Al8@BtcWhMI5%KI3zLd+{_r*l{<5_1)bqdHTtfV)X&53IT)V=;NBIjHewkz2=!+)U61$fYAE{ zLRw3BLjasja>~!h6t_vkO#5A+#RRq?b--|x=y&kY=vsq{evr=o z*y7$%qusWCosoA@RaSCv7pvHa*8UW?|H&QOZeyUGfJ?)bR;g$cV@)yf2=8|mhXtIf z>~v7i@rG`fcNy^Rs#-0f`5w&Dq`xMy2R8afx5y?de4lKxeluSyu^V!1?Ro&WZ#X+M2&`H$_aAEm~>!1);JrijrWb^M`%YY*4P*~Evc)!eDg4R3lW z9XQ~oWN9A1K^w(@>7!@4_ImpylKOT=%YH@c%&T$;s{Bh`He-4wAFOO2%U6l{@GE#baqrNhjJ`6Y zU#$Q)@#$z$I0%SSXd!PEijkN32w=B23y)@Q{Q_^#mpVGdaIKF>Qj|B9Ym$zia>OAW z2l4B%4ox+6uh;3V+H$_G__$tQP{Vm)WX45bCChg697;omk_S?j!;82pUah>CFKLnR z<~NrdFNYIl2Uc+f4abt53f11X2A~kEJxaEEt_m{Ex;gGd7c4ojs|yq@QiB+`cq6&Q ze&Rk~^0jhG_GgWE!&Sf`9XeK?M?GtDDgvB7c3&r z`^1BVzIo%Laa@OuM3+ep)`d&n0?fXiz!Qbv^GKCR}F5_ye3{V5#%fFmwQea-%2{E4X&|z#=1va)5^E* zg75+{|LCRt$mCtY6(^jU^=H>9)w`pZwhS|?Y*v-~^BLr_OILIRv-SnZE1F%opLiA4 z;;loT(q*ICXNw9!PZLh6_mVpUAAipvDj;=J@%2ij!wYZ7D!Gh&b+S6@8HrHU91~W- zMD4P0{1O61P5yrK3h@n%&tBYmX;G$+x#F-+>T{MBLYfC?!OX&zZaXl!CguH+Y*-d;-GGi)Mz!Yjtbz>TXESJjLhxjDC zSB_e=IlYdm1KHRu1ztEJ8cCY{G#?lSuFJA`*`9aN3)AvPR!^*0_V>I(RP0;evT?(A zjrfCW2~=6l9HkSRImF{XY7iGG2>@4*|9I__UqrDFR^CeE9clX9k=l#q{KRHLNDQ=! z&M$yoYKL*zN|nH5@u0la=t}o$Ybzh~`rwpzYB{K(ogwe`LFlAzzD`AWSd;wBjOqsUhG z(NA#Kot)`^+9E?&>V6{VSu`RYuk+@A8EUh&_{a#J+#&B|R&Uw*_hRoIq0hPZpfTn& zQY;hGC`9|0(3PWhl;6$H<4|i6uGWtUB@2ojGNa?UoJ)=chKF!GU5(<_OedDdV&_RXqyEClPJ($&!4NOo`!0Ii z-F|^osB&102~J3T@%eMIxt&hAEQ&HIXzaS~bJs!!dEGC2s1AzG5cBI=F~#ZOOJ>mx zGH7_@$Y$5dBCP9IdU3NndQNG6(jL*cYlBP}wFhklZ=Osal!#)#>}7zH0GE(rL-c2Ne*xlY=a4VbmQ;7x5 zZ5rpUbGKgaIb|PzR&yg#x*O)ESmjgKdMI*tRtH1#Ow}VJgl+}me=5YjV zKoHk&FJs*0nA{zlHWbzeqcp$&P_x_*tZ$DU5%3B=Wz|>eX8c4bzt)x)gtTNO?DA}^`fYugLRDp=c8XG&myjV+UkrvJP?gl2*4Zl zPU5c(7^`|x%dxI|2*)fa5C)kH>I?}sMXjC$3!S>l%S|TEHXU5$dy&Z}!GsO6M1rF0 z6WZ^}qh!A{>%S-8b24E;L$=ivmy^z&_)pD- znfQFX*q%~vjOdbMBAz8jNO0qrd#8L5D4OIgRJgQnOoKRuDOx-|o{ z*ny5r&`%{`22OV15hM>cauj$Iu~;28e?W5(lZLGQ9j&HvX!uhktvjZ4i%3XI4mB>%)bGb?$VYsLYt5g0jgYT;i z86o7ffP*>Fb613b0bbk;vzE>hAY;;paeA|UKoogyG=uUIvJ=5l64vWwU9^-eA z&(D*nP}`t5HLgVqewl2F$?fMds65B^0qgGN0>jlCzNOZL!u{BxFe&QF`a`o2(_^=8 zT5wbIv#N(cielS<*~gC?gHR?l)B?H<)MN(b#J?6=DMUB)ZnGIia9*ycp$B#&kc_hp z{xt&`Cfe#_7k*6+c*~WJ^rs?^8jG_;d@vS-Eo;%Hbe~--;%0!@ZWn7s-^}tqj%F#| z8gk3S#r21gVloQDztRS_jzqT)1NUH#OI7;~auN37p~0kgGYGPOvKIfXr-ihZOcE=+ zd+JrgC_)z@Aw`-_y=8ZxQfe|GE;G~wZrB$dD>$t>nc;J^@~`+-%oXAUdpSa#Pcx$| zv%7FIEXs?q*k!Ci=g*1;qkCM~UmQry{Gnl9M`rS9%+XaO+fRAKEc~)VN8ZG`_VD3{ z!WDd;iPSyFFv0RG6MoA={nT9i5Z12`jWIBtLc`#r^4)Lo)#JaXWHZ|sQcHC^c(Dt^ ztbv+op8`fakCI^m)E~R5LThF5;&w&#r}X?Q{MJn`q+Rr(CqtW>>0Xb|PG){sWn?Lx znfLf}tbm2bWz&SMfICsM@hxLR%=9HYG0lVMZH2v7rpDyqqn^PPW zCgt53nqIXQ=lrLWnMNR}*(6EShC20npbz={o_LB^j z2dW8uiG#1O_c6W&K(yL%=kLh!}zl^0WeS+|8;%qmf~D7((1=tk$5 zHNh)hq0uxZBSQX0!+Mw+a(eTGn+M>^AQn@7Po#{VHE=?hbk(=;Xa2{C;KZuBZgQ;I zpo(II^V7C$lu&R1chtUZSLa((HFen;stTfDxET```MJxxo`i$A3{(BKBg=diX-VyP z?{O$y8;Ga)Leom2?PK#881b2BHz**y`)4bkW%oeQe^@ z*|&k2r_Q#`#O;GpU1kH)a3Qi*Hf3&2qExHKuqX=sT`brm((ilCKBjM|RaY{v~Ga~=R~`O=A^4OW*e7RbTXS-GPX;S>HP;nZgzeg@(g-IjkJcdp1#99 za^y}<1bdVjq}-jqU_0kTrs z1!sItzeKXf20vE@wW8osXYGy^!q`E{#Fuzgau7V1tx68obRdc{VpEKR(4exQlX~&n zru^PX#8?>CusP~#o~^J9+ruhAoPn`6!h}K0+X@$$mwqE9aZ`SXC*eB?K5$EZE{Kw2 z=S2J0m>{{dC5iX7#cw~Vr$WZtf-{9*z z_>t8Ce^O3Eag%AbwR@JIZ@nht&R{`VR$?rURaJb|ThzhaqhUx=7a6vs&Xvf*%cF?&(=Hm{%8ReKz{-YNxX9wS3zdmuMY++j)pQ(_3 zZquWDR@rlpLVbN&$T`%Dh0kwqLDrDm^^){+F+SK0Y2tf6==o%8>8 zN0@xd(UyvVbuZB5jH6Ghl6rT{U@zv6?e>uJpL!U&#ra#f!fQaB>l3In@pckqy{Om@ zJZ6FK;vojNjq(nbA_Gp{x!jTiNJ~AzqZ(tGqs@F*Ps&^-yR6&rZyz4Hj-dFez(AX}&3#)duY#Xt?oK=6mhTEt z8lTKjxP;%ek(czvhVqRMSxA+yeD5sYtKgH@P+0ojS(QdefOt&g9~X?aghi{zeB1T! zq^Ua~ZZ)rrNV&za>NYfJ5J$#~S$dIHqpB%2SVRkI7m&M-4}KTjKo0&WC`QF%?>AqYyb;jPO znCq6#Q08~wMR_CB&axWBmnV$#f(OTcBJyMpQ;`#zR62zYG{k4kf_;G66)g4mg*K_v z4->ZG_Pq-D(>3fEa#PmONo#LeAG^Z1{6s~JeFQcy<7}F?O57^_q%N&gaO~WfIHD!n zzR%a27wGUjxF7U?yD>rE5!HuaUU} z+`PWV_;^yen|JW{jO#}W=QVy=Eo298gig8fn5Rv1>P0h?&Zv}eF}|_;Udap1$Hg>| zpwA15J;U>NOdtpz0jHLSxf}vCytDC+5)wD)eOPWJ-lZ!y=F?w}=&}OOB^bWUsb9p4 zFIIJ(9E?~s+g9h_I4Z3&K6UPb73Ej8Oer_#bY-QV&M;tfo6xRfmdjiijjv~NoBq<6 z#JW(<;?+7)8~<#X*>cc%;*oV(IFRbzo-rw7RSzEQifw<2+HW8vC*_@8DvN7o7H#G;he%-dx0AywW) z)Ln-&Hb?Us2YS0kG>U>|uo-;xecz^WhPn#EHqB@;smQM7ZSY`Kb68Yv{HBW_#N|%y zS`~;Gj9BGsR`b=UpV*imrolT>UQQP>_42mJ-7aZ+6NnP?TclnjM@&WO2_rg2H#PAGE}FAquWR(Cf+WrD_nTD&@YU~)06=E+LF7~mOKFX=9e zqx|VZ3Je`}Sseu4%ih;%Nw%RKd_R&P3amBvRXomHi*8Rh>uyW@eK53Qsa<+TfaP-h~Mezvxw$tADkegtBfyf8oS@}`=c z^F!lmkeaK4F+l$WEOHzTZcuAXHcr|M6rF_?G@$Qy>^>+xmOSblWHBO4NL9yT-?{$L z%HWOJG)4C*7he)Q^OT>|E>|jXZTl@(YX;lzP6_ZDd6NFf4rFY{xX3}bOuvFstcXiIPco{I=LU z-iZHf*ogNqSWRLbV^iC}&JnHo6#Pj1qtHnMld+ZBWb8iVo~kZ zRi5jQlME+3{v?h_oPvK+TmKmAG2&~{Z<_U=w~)sDDO{XQ<{@V7PmB_p_y)#`>z6;$ z;yn{X({a#^`+fN6{o)iPDI>h6(rQ1Yy0#H3?P?vqDL6Pe`npV$Crnd zToe@7LTucSAc0FCSc)4w6637M!f7SPPZalFh@4tjW9RKnE{|sAhfRt~zx#VE^cuxi z3R)Gtn?_Uz&TQP=xNnrKoGEMdYdsQEmdyd^R$JiE*b@YxA@F2(Xw5G_4$7vmQ<$Gw zR9Fa1C%-CUIV)bS2JdgoeE$3y*PCHCWW*bvv%S5o7R3m9bZr{ zBO|b#*;{t|m&nY_j4L0-ldoqX+Dmbjr%!Woa^@En&}vv}DypY?ALabTn?~v3(pGSI zwQ4qSh`f$Ma!@cT0q)Cz;d+yjllzK~_SQM1GIM^PQ&JKWWlxW{Cmxobqu8)JtEu>% zIWS=O=9~Ne2Cy%0L71~0c3R#WzHY8hODI=@ zyqs5O#T)Bv4AKuC%*X=@Q|SRR&L$QXM3(rPLI-i+yrb?n?r-L%}W=3k3-K>IK3r4rJ!)4>HM9* zr7i8Xa_8zowd~t@&xlriBO_GM$zd3a-#gFw6sP?kea8;&5kqu01AU zKxeOUE?Q>Tm~0)X&f@JQ3W0dDDu<{B*|&hrKq7v^p~@`3Kae=#9ZcHJT3uOL(iVs% zus4W_i^t2=x{WPbM{@}ZmVNs4sir2`RmyMY;RN}-SpIBVKcSRFtG6w~`?l`Y$GN78 zyNw#?%Aw%PecI{b6NoW4vUqp$=-$$>$~*k}ATjClW-T=_(O#5sc{#zMCr#zN96BsN zE2zHI!;frE>uT~Yo}+kj`k^9kEx=$%M`gg9^q6BO?~meIIHlsvHAI7X>7J!(#=KA1 z4@E{!tP9HAQzPZ|XSd3X0BX$kd_#w7z2&t7L?0lP8Ei9ReYCEw&M_-6*)m>yY$eKm=S+i~ zQ~k>HeLkzDwPnqR&RbS73Z(tbxpoQH;U~nix@?MjPe`ew8YFp-*`GNY39nzj7OY?T z^5qMAW$47B((MCkp~l$w_*qglo2clHM9V?XK%qgWcU(zUVxpm?K0rx6`;WTroZ6@?hWfRBukcwaN%88J zf@XK%5*x6rK|M>A^*Dr8&>ttsKUU{Kssq`)#9gCbF{CmjNHR-e4zkb~ut4HW8 zjyS!V_sSZ3KRP;EI~61&AmG-Qqv86Ozaig(5woYFqO$g1N7<0JQhtQdC{?S)#Os%!D<&pJ z#;q)&wp}6PX$rZ__x!!XS4s-cmnbFRmqLR+{ieC|*MCGq;BYx`EnMhtNoZ^}pX`lk z=yP8sO?XdP)wnVUd;ot|an6sE6A{@a8a^olIwWF>`*hf%S-yjn?rl zGFV$sBVBbbibi|qbva+a z2L?E=oX2b{Dk_SiQc_Z&Bb5vFt7X|oH*bW!nfI)J)4wd5-jm7v#ke?1H5;SoClQG< zkbIu^4>{I-=KCi{WVx=CvmX0iPo18gj*Y$Pxg0%_BNLyH08a0762&vCZ;7Wqo3|Jr z_h9{t0g6l5E^fQ_=$@`F+rQFxipy79N@JHL3SvKRY|{NhS7)n6P0=LL&6q~b`JhgG zq7te{u*Tnak7EJyqaA8Yb!@6V&l98tonoWD%^W=8$!BF;w?%1}psoxWJEDwAP*h%s zZ+Jtqk6(#97@Q#o-Z87_UhCB)kme;@hhCs>*!$` z8mC=a?&BT!kRc{sG0JP2@dZ%S0gIO1%(?7)xob+Dap%3d;6i>*N8wWNR?7JEZC2&EFTw^}YSMy7A6u)weE>*-^{4RwC6WhC6x} z^GUC8KSl$#_ikJ0p?vMDJ@mpo94sA&()ZrauTe!jpX4+&{kY(uNQ!5?t8dDE&I%w? z*ZlN$`-F+cl^hfQ(48HQO)IKR-s_mC_rLkoAOF($o&VM=Hf|%}lk^&8&R(XHbN)tr z_CMit$;ZT!s0|7#&Hf`8${FwAf0(Rxi^vHwEpv+n{lME8ieWjIa0vB7h*lx)+HnAe!d1K3~-&e zZ%-?gY$tJ~@0O(6Ra|0=GAnvfSS?-5Z!4y;Lif3EjWf=Q6!11kC0 zj@z*=GiJc!n{@A|#cF43OyuvJP_SyKOPd-v zEOmg>IpJ#$1QMDje4uaSNZTtWjS9b;GE}d?y`n{>!zMhjOgo=}u5VW`uab zj~&a9%y};{sgT%kq|^1t;{kwnj5uFXT!fR8^1>ZMG4WUS#4neZE9(>`_3SW6nC289 zTUYk>ln^VV4g~-G;a&0wGc@Tx!)@&oe0aV$HqgDK6E`)Q#kmsv_*ro0qYKQp@_|iS zbJO!s$xb}yPpuSv#YRz|;F@n91$}WQ>yn1R?&4(Ug^@|YdCIse4;Fk-yblaTxb#KD zm3Z?z-*Lu#;ZtaCxIDN0>OR28td+6&$&K)$&RnB{L?$sh@Bq3~P+aQT z#b?};=G>D`)N@asqq4t`S0=xWCidTl4!GNKpcrnYRbS~T*qFSwdEqFapysNP!>eQb z18sfnTUVEIqm&Bo^ykJrn8ygJ)1|0IFI9=ocl3?Hi>vkbZ@cMi+GlLf zk8EA-tmtG5+|950&YpyfwX_C$7^h9Rp@FwL>^>=uXBWI_-RXT)gm%9`{~4EkEg(cu zn~_Vv(XjY6Dj+sZ#2F0`xH`_8kere%KD6Qa;2k{A%iD3HxsbP|Z8^v|7Oum71m#M1 z^1Xe)I+1GqIf}%}Pq3OdajT9v>T)Bnx{Vsz=Q4E)k@^kinC)KQxSJBgN+(l^kso8= zmk!8!`nRWC4*4s_qB@zT`(U+sIP*4%l5tsf;DztRy?A=s*Mqf=7j0y|I}Kb5xX2-8 z6i(1t?qjDHxnBgIHy))i9xf`8pFPL?o;!t6M{`?QTmLmHLmA8MoM^E=E&00j?B-Cd41bTK!mmJiu}e^SnL&_;MnfV;MK3&RfH1Uu(;|~4L_INl8XFd!YTNC0ewZ-(uChUf1!v$&d+Cj^G-`Ztz2tGx6G&Aa^SLfc&eIg6}SQ>BLXO3`uu9IAA}Qwmw9q@nXk5wJe7( z)QYj&JUGR=C$!V<*SztvRx~Gnbhn6nXEL41f|YcrY@`$T;7+EcD$_upQkY_Ok9d7H z8a}`l`0`0V`cBW0+Sg_k$%}*ZmloBkTr7@Zi~~hN^ePGT)D0{zqyyvuDB>M`nNYaW zKO$*&>VmS3$TgreDWRLgKuJvW%8kh&cU^GqYF}b%?#^?q(=U(wk=GA*I)CG~vmJ6&C~%uzj;J?o1i z>fKF}5^LP=UENZj^v`Vn!zAX={xi0;n1;7H=KsuPS6%qZ;y6c{tUrAc67lP+xI zdfKdcBZNK2izZj<6aH90qH$C-o6i!^qGF#ij6Wz#6Xco8n=?9X;&%?HPPU?X#4OJm z`+vZ#y2%RccwU%|4f$)pkwH_xkdow?Zt%;+=V37`TtyLh6svEPG~w$7FM1 z`1(rxt-^zo>gT16Pp(VV32#^r&aXv;+$9u?W@v#V-kdQYI@)S>Du9Ibz&(taP|R@+X8@C!WqC4nlIqXg(an1t z3Y_m^XpAwDLvoUnPruaY2n14nHY!#PDLQ>52iK1j6fu|JQc9=G)1Wd+(BY6tfU-?J z6eumg<<20O9pm#|HMKry9Za&a`@f{_^K41Ar~0$$=XtZOrtq0`^!+(Si`cUy->=J+ zo^a4y-}jyUXv#4x>Ke^&-Gi|&3r55!8q(}m!KsAETC?b8);7Dm`8_dVb2aul**d{M zws6WDThKu5B9DnL9Sig!R=3;5lf|yO=%S?K9@U*4|4{4h-ym@SiK7|U=ZV{6 zY1lChC2{FRRQY;0?4KN&Wubj0PnTm?<5!Nf2=6Htvnt`VsGEOyyX0gsN0pCL@%sUZ zl&5*#mM`Bagm+^Py*P04eq^6k;$Ga}<E{v1Bohi=x9`K#@E(x4Y0U42+^)$~O6I`oU{=j_YMrFn z&R2y6Y2MDvxCqCccDe+{VZAlI&HfLta^1PMP$7N5c2L1pXefK%n2W5a66J^>OU~`` zoPIqo-y!i9*K%Eyul9YeDjjP?%EP2{S;YF{k&gBK4JD!<+w8<=RiMshcKbP1 zn?YGBuDDzIoc+ItC7*Rp-?@mE>^ByO!KzIZ5Sk=HJ8}|1nn!?RT3W>pbM^4aF|<=2 z<;LdjQSE2xDsbREw1M&_F;*hReteWmSO9eV7V6b_b`mWc{vr$=FUw7!PIUqua@~`@ zRHABL+R~)h<=&w)gDr8pJz_r26-=eQsN!7)s(*(QW)%wNlh}mD?|ckyv--2YoZ*7xHn+B6a531E zn~4Tz@BAW5EM4{K?E}uJ5@Pdre2N9tJyazkIFL;-tMCEkr069k_E?@Hx`9Ukp6YYV25Ot;e!X}K@c$tCJzq{a^Cl_UuDSXkdw zpk^=Ro?wykJCV81+Sf)ajaV5rM|a=sHtrD;5(=m6U>edEPzX9LCfSyJ2Al$ssn4+L z9AIAk5A<6;m)txPl67BFPsSvWsON3kn$e=MPy6cbV`!OU-@2Ywkdlp%4#fuGG8Yrm zX5d41LBA`H?W<<{*_ufc?|w^e45>q^nP1J`Sy@@5qoa8$Ut}|{KJ!>l0bSo>D?DKa zo@O1c!K%ph$%$JF@~SUvcqGdkTtWNQl+-hoR*BsT*^~U)U#~S5j-8_JiM>of|j~_pNoUNkSs8i%N`K>M|FE1}Q7wz7?@@?jL zN_5NG;9%5|iA~oPQp5=)=@Ta8(I?dj!4|Q8`YL0gxkv1S&M1(#mpz=)R&fcutg2iR z19dC4dn(Tci^fNsQ&(cDHf7b&g4)*Mxcfahm$P>u-&*ZiD<)>LHG6EbE@mDV>k&fm z*=72SxstJJ{Y@?VzaIUK)=|a3E+foOPhUKqG;IY~YA6<|j+WL@FH7m1_g?e=A3WNj zT+hU3zXJo0p4F&s$g1+IK{aeFUnAT2#f$cnlbpIbK`N@Y^*a(0LjE^bQB=}X@sN=J zP+gUBzAP(O{$6rU2N@Z{uf_&`;y?#=>FBr4f8kzgi9slFAJUxw0LBD^)PyF zr)wJ;ia4*x!#jxnDkBI82#|(K?mhAo8kQC@`}5Z%h%L8Y_0#z`MN7b8of#qJ|SU^^Qt25 z^m_l7br+APU;t5jhT`Rki{0k?^Y$ZO^65M2lR2Tbxqa5$gfiou)Z*f5g$p_s3=9kl zfOG?&MpI8~e<~Z^v2E&lv@wkH?2m-JJbE(;8JSNzmcX2IADjEU+vLyBJY0d;kj1VR zAq!}k5``cs-`pz&y+4o5{tOq@E5%Gfhy4U?MU5w&`z8E;*A}Fp?wYrU00J{g$^ERP zs7OgsG1ETmhwC8uyQwL?17pK$^1#~ohNbZ#JPE^JIDO5 z!%aEnc54gf^Oo8}t*xvoSU~^#w-$~XLV>alG(!6A>Sw(c5VV{+N>xk;YES-B1^#p#6PTpoYNbMhW?a0lt zC#k)upcYYXyQy@5q@6>c7@ZcgGmpp*mFIqjv_Yp#U@(Q?%AT-cwsDR@)Q30lfHpRn zE@Q!}difCFk8AG807qH&Ddl$vPw^Qai|iKyHa61xkf52nliccguoMxcX$Ght8Zy4z z?fZypD9L8{bT`HvEeq{X{SI0)#<0P`a}v1Z)yW5c!k$}x)oj|MQ}L!GRyIGu@Y9D^ zGk31;2wk=O;QM+tOKn>)jYH%hhor|L&NaiE?2(@dO4Yc{Kf@v7T;lJ2U3JV~TcsVg zQxhZ86Ld;LFsZEgu04nRi0vQiN8e08plho|#^x5h%Y?;>6ZR}30IPfVUE?C3kc`V~ zYMsbCe<%!64LLuIH@!;KQyVKA%|FMwT?zj_3!<@aYJKf61lE3ZMuWbnOGvvJimLF+ z@ff}zT6v>G8zjIAeiDCyK%W#*@@mF-`^TMtg?GGvh-k~IjauFM&Gt!=k?YYTvOw62 z$CoT(Twt?;kl%+Thuv-boIA8|$GdN*3IVD)u&0zeOqD549HXf==-;^!kQ*D>WCyLf z5Z<5Iud}u#=EZOKO5&`f8x}k0;xOhU{`%`SCd7{(9L}e1F@;&zP6KNTTiNqF$oK!_ zzrt}^TcS}5m3Cz^*UKgU=E(NMDxD%H1?sfRc$_9wfPA&ZzaXfDk?MK=45SVEDCdb8 zus}jukU~~OhZRccXSgE%u$Bg-6ajx{wd~@HEVRmHfBDY1K>jG9VY$Dzjn40Xu5^Q<I&xzS}Rf&f6xN9V~xNyGFmU5xNFhJY^>-jO14=JwH zHZ3ZN1-UW_ev^jVCuTUiOO$h)9xo2^*DD$@$k_G}*`ow3EN4ef>P0KV-+!HZwxJs% ziB!(E#_O(5=?;hyr=?}%jF?d~OF-%=C5bZQO^(NP%+;pTIsSX@*tfiTnEmUh>W#B$ z*uj$k2p&aoDn5fQ<$qLD!Ru9MZ_q#>LTe>4}LBKrIrH#tT@MbXk)b z4+NDyE5s$u0ji!xOK`hR|6fJBRYGXCs8JV(awo9(Hv=g%E-Z-i>Y!uVf9C1f;?ggj z@;FcFfyO951hx354WgTK^J{t=`z|ASar?g$)Kwi9`m6h0QJ08zlMfgax<{LVLxQd9 z7JTxuJv|8dHh!=A^pzr1m?Hsi_&F5ZKneJpB6%y_g0I=H)&F^+fWMEl3nSj{EbrB0 z2(yl2L&nCa8f0R%XkSJ)! zczJpER$QTl7AUQlGQcH=7&w>GR$eE^H=ANUy*+iD1KX5YW1gR&G8{T!jJy+&u!cPz z+NY#MFeZefBfQbscK7x$+D@(6#&w`x(?e#*9mth@-aq_Zx#GFSFG&Wfd*oW28$vR# zmx*hXFOyM?KRouIhkU!A8vBddMx-&g8H!2hVG2Bjq@&~KYt97d{Q%WZkMpvQT|Bi< z0vp=2Xm8IU=$F7-yU!d1|F5; zX)ezAk{T(bswaJ|!l>({aR4WG1m08)(!#}>f+^<~g``lD+*NipSx-!etC{l$mg+<> zK59_mwz@KVWb2H+{a{Ms@~4NfLETZC07OE>s&kXq+1?u(%To#)imO4ow{Mg+Sz~@b zj^$2!@sTG_PD=7qaW*9rX1$rtbbGf!oSbJ0e=gQ$E}Lxye}??Lj|HCND^x+CgWK_i zw}r*>)_9SrH-gVuVaBc~v<7|#3WjdkK;9`JMpMsj*FLddp9LqSlI zi!ct#t^!%Vw~C62w6yfgm()-kV(x6DEGa`k!yaMAOM-wmQlj&JiP(vXilW>D7cH!E zwRlxo^h;<1V@CiqA2LM8v#b~Wv*eIpSX6fDil;jE=hx9qYHDhE5kWz{uR5`8NA!hX z$$>nH&+BMIV8Q1wD^e_p%c2uVWme{aK1shjZEElVtLx`g46Yfba^ZRmfYMar>cPZk z7oX|#pilT80l5n94+VAxEU@>kSqy0+WJ1o_#l?0&((?K9=hfBKd-v|S0<&W5{fImJ z#u?6hP!vKccC zWIlhJ)>FMTH8rRD?RPjWyQ2tLbd-~Lp>2WpEYpC1V{LVH9#k5t*4{kfYH4CAadAHm z-`w2Ks;c5;X9ozM+5d85uV$9iIfi16gqvRad*P)6L!I|7pQLC|{;zmIE2D6jekrQS z!dId^a`pN*q%}fIS2ywrSD^8J2RI9T{pmvOqTQxL)tE5_3I9t(Z6d2aE-7iBgM-EW z6xCB8NHg}oKCa@lT4?dLohbGM8o&>-G}r8DAD@aQB_yo52#Tl2f!FKms${u?`JYGHgnsvmX1hO?+EeexT*g_lGVnE(;#ZNQ2<@+?%a+ zJY4IyM@SgKr(Xe)k7Li)_I7E*7ITG0ixRj{!4`ZUvEGhMJ=u>nzlc+3Jwu1g^U~>b zxfE9!pBL3!+VjD^ye>d32CPW|Tuui}3=B{fKC{1^qXO15ocUb8;_XAC+}zv|;3d-^ zEhN_fDx$+8kZ(@yMFglBAPsDqgN4p zAKwC29~JSVJDO%w7QEHF?_TS9=T9M5A$jL>L z3Va0W)qu9OzPm~qD29{UGg?`aLD6u?Msz{Rn2FZjvu!VLjofdFh-hv&NWIYFE%;6% z)#PK<-#^4X;`z?A-rb{UumUhk?8OO}iqqS0I&1M? za;09igFBm0sWW53J159Z#vpw!;8*slcI?Y31`kECNnB)jVbqP4_BpkAQLRm4qxNv} z$|vse@eX8Ydk&e9lM&|0&nn&qtKRC>VmE6hQk%cfLaym*2~vmzM-pLI=ghX<_xB|C z?OCvHSJSFi@z}F@5oAnq+WTR`B;{o~KrOr`|2*oCqU2{RQ!DrvJ=4{{4CtLER(>`Y z$R%Dj9_;{>1z^oy)ho>!JBs}KMMbR{+9v<(Xu?mb6q$ycm;g*03Z2VAcn??hsB{NDMN) z^aJ|%kM1m#!2Dhf%jgf9nvJS?jo@l=v|b%Nrjh;8(XqO|j>KB_nooZLS?SE!*bg{Z z3^KIxXhCQs&XfhKN^YZmb~Na_>yx@W0WGhFobWQIof^3rvY9tfx`0ENW(W6@lny;Hnwl|jaoNL*mL4Eq-ZRTtK9-KCK7ht|n+|nsw#i}vq%d6^Pz?pwmS3}E{fIR2 z@8!T7=UYIre)?**#$HKTSy@4WSk&uiHPKYeZA&W!%titEV#zRji*NRRun{Sqt=8X{ zMo!L*I^L)Oe%##3cm;_S<gzdkU>yCnrc{ORr*nk&9mr7-C6<6#7=ina#;;(loNRc^n5{gY8#%BrYgYg^ZOG8hUV`uxH$*A)YP}vlc zYAy&T1Q3YboC!iWjKHr-7BpvOz64R9qPOMhKq+fjP?;Yj0KhGpBY(YB%K~rVr|mPQ zQqowP7??#!67uCytX^VX7OO$6<+T-I0s@~~f#5wb@NUu=gKDxz4_)HV_y}U25CkeX zKWpPRo_+c{q%P7)uOAE)ph8fXSwfm0A~Za3YeKN6^3f$Qci7);`0p`{I;2a;t! zm}&iW3Qfc@ynx@zByiWdKsG{(Mc@vly7C7r3q;;lva2@BTlLFjs-u~*H8 zD5xgh{7tsG$oyu9mwhr$^IGcGtMfD}+B!^?rpfvKoID=#k6;!M38o68zHUa?z8tXQ%IT|f zaD{0v2mNncuFhUJ&I6DYBpr$+F<;at0hUGqum%(1A@woJ_)0b4-}r~))(psrArcs! z&!OYavg~{!N+j{lC7=%gp1N)Ua%!UxY;v#ZSBb^|sy$+^$8ZC2#cxrsdim`&mRwN| zh6xZEwf1tql3n7>horMU_7W44xqzrY(!S3e&plTX$5=eMZ9S0esX^MUa;qQT=>?Op zYfEps4o7xM&_z#&P)Dn%KD~JWQRHQsX@=eMOsKd;FMQx?*K03pY<~JKs}RvqSD*w) z)elD97I=5etqTyNK79?9bBC8(t&CAud67`x=yxYm^1t7iP|Vg*`=B3yD#f_n9?G+t{=!Kzcme7z=$^~Lu&y-)0luh1c! zb#VfoIF)_M20tb41+W6_tQ$SXm(%jh&QpvXsa8OKU z^fEWaPagGVbjpVE^5!fFkq_=OM_=tX(sy|D+0e{mK_r%rVC-yw3s`4PuQGmJkg|SW zTwDx%u&f9A!-vwwB9MTC6i4Tll*yw~>4;a(ozcb6g_2#i2@Nu+!h-@IhXV06v8F>>j-u0!IP1911w;=74U4w=4 z5W*tRL0)q{%1Rka+VXxto24E8m#8E|Xja6X?6@%p{x3)mH7NeKsjlu5@6C3Eog`>8 z3V6vU^rKYG&}7Q=uR=?X;>R7@baAmBS9|6ybBNg^(se}yP&UOMe3^x4D#F%1#b{}1 zd3m++Mx$jv2~^x8;D$hY<4m_bSV}i@=0Xtbc1KMMpND6<=sy$YP*$LPTTHOi9E~sC z31lvX1%Oyf_h9?@;vsw8VrDpdSVRO}gi=#9TRvzPQ%|>FFCN^p^;nl*9)@l1>6e8x zvQGagdY}7NL5~Fuf+iXi;*xkm{$vBphYrAkK;|`OHm(LSz!(XEVsVQM3bIJ-$vIK} zvkZ(}PiZb7IPm*!Ov|&_*L{mrPDVA|sAR0}U6Sam^6#)D-h6<1?EW^tkYji|$3<_W zjGCK#H{h=m2hBfZKo%P7;+wI0o*a^h&FkaUYebtS2ldFe+P7IA!N^q#+1w;n0KuiR zPM&`PG;q=Gy{QtV{Br`}TyhIfU(@#R`y^sL0u-;c%Lm_2UJLgf^}TMxGoqkIftUIVpP<(5>(Dxf&Wz&h3ce8q|USe zf&O9P4sS7@7hfVlRtkA(+T*+w+@dVf**AHp|7qmkX!?m8-8{eNd+NHP?CoG{)r2pfEFmF2aKU<~ii zS9)SWRco?g{;SE;*x)=`=I`I@J%1qWr&|zt)r*hYVwjv=@Rj@oF`GAR!`^!3T;aPr zS5MnGdVL3SH(2Enj`s9qYE0Oq?yjSUQ0;!--G}|#$%=eTvhMD(k9xLOoG%`c8+Sed z+#T?mUUj{@WOXmtnAodHF+)IW%;eZu+4j6tOLId5X!?3v6^cWlI>WX*w2n<%x4ydi z%E~I_?b~Psp5?(VaQ7v@*q?rd0|xbcy{(hrz;e`|!g0p!myT6Ju1U0ODnVAWuksKL zUJmZb=lupw^4#ZlB+{MKSC6`7W|-gKZuIFdQc8QHPrTkQE2m?!ncVBx$ryRTQ z0HeKa=+%{vbNm$WW}vI6yXGzg)A^Wx-!;X;@w0t9ai3h*7jU&=vTp*7DqEOqmzuLw zNC)+Bv}Z^WBe?g5(fo9pc0Y2pfe6W#^2q1%Q+hqd#S@wXVF+p_C%O2~9~Cyc-%_Zw zU)y#Il6~B3{hJzmyj{}B{_NQmFc45RQ-HJ>5Mk3#Q`FVf|A4&{J6jCe-`}UBqeE5! zpo&o0HfKx|k&%&k27m{ED>ZKw7Ill6f^hvFk0F6o|F>(+sRp{j`ATY|u>Qo~xP*kx zj*jyGaEJUiTcNUfg#)>Tl`h9{?XN++TI2jBGt63s!LTptuO8dV&oX}^XIuEILZTh0 z!S|h3HS9M~{DDnol-|=SS!Viw-8E>>#jh43CrZO}Bn0VX57&~#KEE~9*5>m$a{`2G zN6tw|NJu=3F1ub?K(=+mD&RKf8r)bErd(~Bc)G>KF@6KekEmyqHof(D!f<64=HJyjhfh;y=n3kjZ?E7fbCvT?;2ls}mU{<7+HI$0dQEO|&#LxXv^$ z!#P?!`uQd;EiH%GJUu-re2<0!ct(P3wsP%$S>ANe%K|zQ)5QI60DX)Npv1q|r`{oc zX(BefNjPF>l!Ah1pvQ^sve>w>ro4O~NCfC+H{H*Jk7t-zg_Vw*C`fi*=1l85JZA1r z)X%R%KT%cIojhO;N}@3p=F8J9!X$BK^1Wx9txQ5%VH_-T4PWg zUSc5gRQUYfgm1a3)_eE4*K+G!Gw;bo;CAy3e4?V)8^c*3L(aL!-}RBasI)YV`<0@h zNV4E-Y7r3;ko{NnM7A7_D2bm%i#AC1sk$3KT@ z>F1o8*b^Y6x;Qj(JLL5kxKeWA1yuG+fxOJI$&)Gu?0S8nQD-Hw>?*?0hxH0DC4tu@ zE$-aD8bL0XLbe*u?e%OJG2RkkjmzD)r_x>*kME9}D>cAzLoq0nk zo6_6+}*SN^~$O3&(f^~V=(1HP|n9x!Do@pWkvGDVb&dXXxuYHCjI z(_;}SYl_s59cWr6lJIsZ>8@&cGM#RZH+>|Blr7(Jjwiz`Ydm}_b3$;R1fHO0%Nm%J zD7ADCb*X8@rHw2!pIn2q8eihs+DWCTkl>eA z_F_S^jTDz?3k;JrO~4tgxH(UM7O**p#LZlma`VY|RfuW8A8nr^Y|!Z4SaQ~5`1@mL z_JZga_5a!)#>_TD@1Hqg*)oW79W>@*oO7iZ+E#sf$UZQU$lgmu0rmf>`Q z?Css@4kFW^C8ubh2hC9w^v6g98c3!bYJIhEtZz=A&`{rFg<7oY#$;!$m}}r>RXZC^ z9E@xfbm~IrHPW(0i(b(A3ZaHeus;ufQBqJAju=y_BOcd;C;o0so>7n$h)QEjoQ_KC z(-h&bpL80T=IUu5a!O7z>c#S65F)7Yr#KbPBCcP$dEM5NAtt7v<-+WYPU*YPTpgBW zA9LJetKgty*qzdc6L4)af^(oD)76E8@?BQgsKi)b*UZ!0A7yr{g0e@#@1f@>BN8J9 z^OT+GF`aTx7T%;-29)dMV9VE=S-1_)#hyGIcFORAQs7QYS8M7CMENs!d^7xiNev0> z^`9aHpY37J$O%(<17G6vk%;E}c0Y9+*d7hQT~{jH*O_QQ$INyH;Aq07e=?Xi50tmg zC75nMGhSybK7FtaI>tsAwK-%IQ8G2BUkAUT(QQ9=h9w>f_Y@llarG@Mb+F45r^vub z-|t}$%a#atYwI$-RiRVS%N`}n;kGHDP0}%Pb+b&nvSX^~+c#)AQbep0zM<8ffj^v( z59$Y<%|c0)uDhy#VCT&CQQmaoQep;CAIY-ER3j-?N}?)6HVLuv5~ZGnACC|^XH#`~ z4c4j6cpb1gZ;IC+mN-Aq#F>3QOqJhF1dTO~Jz^6=vn`9IWItvQm7q4wF0UNZvDzK5 zreDc_zveswb*73_2>R%L zqr*EYfBlV=pCrMWrsnhDMv% zjYIlIQN3TYPP3|5#w|CJvCIQ9ZHO^+04;P%9v&5zm!r2pyY7d3VZXxptzhmH-Q6b< zMK~NVtV%tpe!|1a%hO%=XsbsX{Doa=-6$SX_pE9PGYlKSfIQR+{#R|8jOvdzA_#yeyb%pYfj#`Qsij81+ZnoBJHAIFp6 zH)Uwmx=YGW<+eON3|(q|MpX^W!U$HY#|khwo-fT}mI5RZL`F`MiB?%Br2j5axSt?h z%2?;kDzvYKn-l|XS41}PLTY?7dgjQfKi{c;0p+72Rz8<@EqVv1Gt-*08qwO%)gGfL7LM|DW zuUpaw!YYWW$HnGCsfew}KZO6kbVNyfZ2HIMUnUy=1NqByA#?F<1V#bU&%W@6!)vTz z3ytoMFxZycq;cq2iKWLjmFlFBYARb~WW-0_dtT;F-qH|$3V`r zuQ3zHdA#9-)${R)p02X-z$<*r!R>+7re=d8%RET>{<`H4U#CiOm&^8F!xhnmxXr&j zEBjCq!!3-py*sAbq6bTqt46Hkbw6j?dGy_lQqqw+(K$Eqp>Frh!E&5q<^r|-W_Ya- zl+`G8Mv27k)l=PJq4VKVatG>G?CbiKg-^>`YHAOgMMdR*J$m^{VN&2Pm-O zo6^L2niCna;Zrz4=~8Qrdo3(1;2##v*hbI9vwmCfB0>CSIA4<{IB}`AdJ!dN)J4iW zm&$#)#0>vs5i`cnwPn?@84_A37Yvhfy%8!jfIhzQEPJh6P+dHnF`mC1cqW8#ali9) zW}0y&&{-Fj$ev^FL9(z(2#>GMAvY3AB{Ps8PD{8{y8rZGAFa_8=UIwvU-|piY)KsX zDeZImhPOIp-HDpu@AM_Zdw0~Y56#+v2w|PXuE_w;cG3FB_AUj{PGyU@1ZB$qyQGq1 zIIROE*DKk)70Coy*GC784du12-~%bE_kdJ$zQN5{xKGm`w8h@N`Wo64^0%S3k zhOuN^r%n&uW9XuSi7D@#1Up{fwRqL|%nyn|!$YMa8tU&B5m!G2Q*g!9C^1@uswc_n`>8bVjS3^(@3~G;78t8kHnLRLmiDmk3OkCJXc9kt`q+LY4`(=9Z_wePygbAI z+J<3|j@ujMzNAiT%aKqSI80)d%N{UfoFZQnoC6q0P+nf1AEi+T@!zgrE9#vKOIUBB zs(0vpG*72+p}&9e;Z2bd#iO$qG8q(eLj?b4RcY1w>pas+i~>_r0?}OEzjf|YIErNF zH1}~+fsEAeAU2JWrvgR2tH_tNQIz(bDSw;m>_)mo_zqxz{GgVfIyWvLLbW5Zq}Q?l zoXI{sKV0WHAf%uebU;&2jjhH`BNzb+?aJQ-8Wcbu2N5O8Tn-7XI0%y_7^xb&$^KD( z5)%_s+GPK$Y5HZfs_eaVUhiHz798HQVxC5){8Z$)z;7HMA}2;VGZbqUWm+{rfxCHy zG(1+;xq27tSFdu$Ve>${IGiOn-46*fcMDq})c;%Fy3}_!T%@{4$m>LXyyN{v{rg+e z%I}pAtrCL4mRyC+y6TvXF82i4@cx;Hfr^R>WRZnMCDis`6WksJNX79B{dE~pruK%v z*O~wg6@>J}yQD-ufXVrs8_JOMJ237{pAfvEt6{EfadR6x;n=Bd^oQS?Z)LN% zo?rqMC@4#3TYSAi!Zo-LfJy)lF?L16HjSi8$?Sf{CR$LC6yKaPF4N$tv?kd!xCG0S z;HYp$M1Iep2YUEc;>Wod&TMBMW&Yuo_=z(LmT4mH4(8@dpfm{#3IY{cpL8e=fOQ|* zb_Yly;eoo=rL5&O-1854%b8U;mS}&SlcBD`vPEl|M}-n%v4Fo1@aKWn$Enxw&b9Rh zP}sIE20pB)sQA6Syt%~%P>H6M7^SEgNeq5`1D#z*J^xOaTiA+(w`!BaChBMeO+>jY zts7V*fRjBe=fp1+P&N5nVD81cckhCOgNsUAl7c;OSEsQcD4`(1yCj9DBtDp?Il9Wk zUv9@!NyHp2@+?LG{r)4yxxVh?LO0$EK~p7DaHRhNMG=^_GkGV@yq%ck{Z1Dwd+@t# zFwB7h??^rY>RkNs7r=rbcfO0dsU$@iO!`0PRwT1s03r%dicS$g?nP01tBOYKK+ThK z@RZ#@Vmy8NeV`Kj1>}u|Og_Edc4`#KRDtoM;GLbFj$vu&B%MSyxeRP>rJWZTa3uq&7=~h-&&dwFnLF2GjwRet6tMh(xtniW*B~V{} zLT9Ql{T%py82MuMMk|n807l>mCK#I2G(kEY$Y+`U_p1=pBz4qDkzGG4`ubqW<0-fY zs14;M7@#*xkV^Z#1p+(NVzR`zSkUU)YlPl#${upP+P;iLAjD@W*?Ps8r7GR8=h1qls-Z3fNk#5`+x?3ci zjsE>3xd`AS+e%Q5ngMy=i-J{8utVqGkG>rAnf~9O^dDjtT)&tpsT&(OY+?+Xo}OOt znyRrURgHY$o83td+K~4zU!I_TA*=#ocM|`D*(5*-0w>z6ozWj_Kd*bxmsqwd_h(znOKFr$>0P#L|$K+A| zn$ZvB0%8r4vdTv_&L>i`2kIaZvX7zw8l9%M?qOs~)eL?LO%H+Df+-n`|?*7DQOIS}r zOe_Q#EI@%qBV6wXq+agCz4E2qD>nvKIYU`Q8S`<3=?+Z>pc14c2WunVfxm|+ob5adLRV8lOIoz(UzyU6&t2rbv+ zMOsb+4PUXR)K-Qfsr}JoKz0?4Cjr{Sea|Fs6i{zJg@}G~eO3ly6Iiv^QqIF3!Ws&R zK_7T;#cJ_U=E&h37EMGVW_uVK)kG4<^D&=ek6a2{214+x^6#U#DJ>cCGiKL+|tTQM_1P$;6y;OL)Wuw zSnKe$sK^2whB7qv5cImK_EmFjK2EyNt+Fq8pBXH1^z+OH%Raf-jrU|VSnJ++btJh+ z0(OZhxKCVMTwpvH5(@x)UoET+6FIeZqu{3v zzhp@pdukz~eF@&tFG~j`VaGsrbrnMD@JYnIqOc^Two=Ho^M` zmT{HfQzSU#vBptt^*a!6TAoeD;Jy&)IP_6g{s}rasA*`PYv7}9Rvkj-)g1_R$n6;O zW5r5(-i`fw)|~;GqT0B_$4Ed=YX+)LQ?SR=}iw|1Q-y zFJ?c(8+0cqYe>bE$e@KI0cIiPHrKoyC>LCD!2?zaNG084juFy(3?bkK-7#Pa9BnaD zp@keT1r$lPFc_H2^?Er%;Fs&DN}`eTd!2A*%Z-ms%%Bf2i7b#;WF<2r1j%$D9eIFB zG;#k+m}E-UuL<2%Dn$i^Rpt31Ad`TwP~Inv&vv4E(nRU|#kj6%L@bM>nwEaBE;e6k zel)gEXlVXkH~Is3$^+mGAnCfw?_N?Y8z%pLE#!7|57=9?iMU1Q2PM#gVl`c6-VVBp zM9XeIyjmt^f!YX-56azBaV39%G?1f&rHmwXa#F&dh)V6*_?Vdp*=6v1$V(3=Pw!Hl z5dI}TW9gz;%@zC7O*it}7>M2Af8BWT2?!@gU_Q-{${jhr2M+Vz27y)H83+bh<0JbH zAEN!ue4LCPA!&IqI+l9Wtt4ko%up6+zpq9IG-oIC`m20F>)NimvJ?<41+;LBg|X4R z7^581yR?*I))YC*h@oHK4kamK)wKWlj%UTvqWIL(ed20J=&VV*VKO5sW zujC4}v~zV`YCmEtMt-{sxDp_#33>Zx$t$7A<#G+P!0*e9J^w7gzCb8ayp>)K-C%>% z%-)xVaju-CD+#Zd-^#i8q<864FfzgLRce2|Og-)7O%V^IVK8x^5OiPgbzl2Q{7?D` zhf|tg1C&^=5pMtD1>P)bkW7%V&$82Uj{!f%apH1d58Xd^yT_|0-=XKd9zAt^eYybJ zs204ozv9s=gLq&Vu5ozcF6g#3c5{6>P4GkuwB|Z6Rt=rx^n}!7{CkgUDxR>2>NI;N z9Rn|aslwadudT4KFpVtP=o27JkpF^IX(D#iO-<8cV|f4+?nQw1_<-6|q_q4OyNZ+w zMhETf)e~RNs@}ybJAEGg0W6Z6OJ8k3{`j%vj90S>eg|PSJ7ULa@!K8eQBXu|YCn+? z6MuDHuIv@PF+OL%DQUrpK>#-8LJ8p5z1oShLl zDOx5bsh~%xvx}ZF+>45fs{#rR$wwu1zr%bl zPlm@FxIdGSkWAH{OYMfaat&Ut=Mkpp8W@le5p|Uui8NA*rP*GqWsH5~{o|ih47y+z zhW5~EDB@6%n#%uEMD?a!EFCWo_&<%7owhhCbR;Ah+YS4ynqN|Kf?4$S+!`r`iI)>}8Xj-2^W0gS8xuFs_NRBG` zgzKe@y}iAkpP0~&iy#@OSioGgBSVSuEyV9xxg;A)3M^4Cb5HGcZu( z76#(XHa2Ev7#NHvobu85eh#z1!@Z|aZjo%MQXoIKm36gWLuSAd9L|wXUluo4g28+x zjk^s%i<4qB@&-<=DmyFY?xduudQwjwXLU{7H=wzRsZS-m`>VX|eXc~B8Texeip@(* zC4^idnU1xiDg{J`m014Oj^x8y+Qul0tng<)JZ+>cx}Pa z#cYJhW4!`V{mznai?#F%&iRTLXH4wxT17>>r+gFv#}5g(!^teQY_h*EQA*eY@WA{2 z&Y7M2`eLLurg1NmpN1+Roh|1#YF8iUd|N4)kfsRgvr1_np3;5gW>@;L#~DeaBmZtE zew#6EC4~h7;@!Km{V-?zsaw<5$x(W=IY|RhXh6)&RNh&o{Y?Wwe8$@z>0mL%vE2GhAe}XY7!f)N2IVx6@QBpg> zbLsdr-^Zv*2)MmoOELe(#^eUGuZkiChEJMMub;p7y3K{v?*=82lD8pSf2o{&V;pli z0_rG_zivvBJbR1GVahav_P{59;9>TxM3{Ry3gXBhiouHi=gP+ z*;WJMLZZC;k(gaDpqhcC%P3H7p-;kyFIl{-i9s{j6SCg^5m+SO0W5Om?6`MH4*~vH z5hTu53g56=w6@7z5xls$)7e&)XOV4nS7R5N9O?cT2v?BRq$llRcd@mn$(vsn(#cRf zL4B}A_E&6xKEos~KTRC7NEBqD0hvJesq`iIP(^iS7b6?4E)0(DQhv0Y1WGUG^T>bO zCM(IpnjI_$QP2G#|8;_(3McOLd$1OGv_bUM3CF|iH*nl>{{&bvetY2$)zFZp8m8l^Sfo#^IVol6y{kyAuJDFQs@%Vb6w0!^NqqgEB$eV8!#)VAz|3ygtra?`7;RqYehd+QNm_1`w2MPdxwRh3ULCl?|% zZ(oYhS^ffUG75AgcsVvG{_e6)`Pgbe=>vJfJ3_-t&t@oP;i#{sYcGd+_w9_Kl_`Uo z6dgN-!>2C+<4?hHn{i@H9AwexMPp*{R}I_CBhqC+4{e0B9WKzyUF2%~i+MqVD|!ht zF1_}m#de@2knUoJ4`V!iZI=u$c~@8!x%p3Ia_cm`HG(Qb*A1j#_dA4)*RM`r^JB!5 zWw4XHQOF06a56?jjzG_UG1-G9UKLbg0EknNT1_f~`_JhItdC>M`p7UJq-pdh?aB9n z>Z&~J97MNtI;&xreOlVfSoP8;4oyys^cmn3zftlFg%I@Yfa$%h8hU#^+ww1BrIU(* z?bQ>7_3b}sw6m$=B&~RX4s6h41K%Av zX^+QNa#yPXGBuQ+zaLE|bNatd8t?oWHu{&B;P(B$`2$-pctxN(?sjMd+VDBQ!Q*Cr ziO$H+0#N!i66YiLA{1Gb-_VE};q5SA&%3vLdCvV#jx!3_JjXge(7|g*QDa?!0{FCz z5j+^+&$;Pw+Uqf&id55~q-j`A@%3S1zJ&1pNGzEi?3--HRw)* zL=L5W3tzlPz+dNE?{t?ObngjXu(kzw>KjKrLw0I`dxC&lUKKgG*_|btvm$bOLf%qk zUYjzbf7JP!k%zfx&7D6d1r9Yf&Yv3%%SeDMhBR*LnHB&NUsDr)8nPsucRJed548$2b38 zbOBd+3x9HkR74S&LO~(h_r7{85yh|BUzfBFLy31u@cs>tMMtgtJzagHlwq`ZA-{UmYLv>VFk5QWXS95{xf##=9r1hOFL zWYU3W8cTMI&bmYl+0@n23&LPJh5zMNq>hNZh;r9L^kdg!^nch6Dw->)1u_>5=3l>E!Dd5^MQUXLXk29SV168%OsnF!cRo6|( zURcg^i+7vSuBQ74<{-Fs|MW)&juu5p<)^igeIa>XXgcLF6r!F zY!rFR`4o`>`tFP3XbOsskXiqoUZh*w@a5B4nx~1_qp(<)AScZ9^NE4RY#&mBM*}4I z-OyR=7YwZ`T^o>1V2z+e^*mUKrjhIZPpW0c{{@u8vi!K#gQ+>B8A^ct0Dja|tb4*! znYSQOg_BLWQwZrMAhfX-dN7WBm(*f}9kdY)pt!oa0%~5zsj@e>vxYN{v1uFk<{MAJ zYzrDvB(kd}cAlM?8Mup?V(Ai=mc}h!TbpV^PEu?VLwr@K#Y%#_yDtbxgOUOwDM*VFk|PaL($dlm(gMQJARs9sF?4q$A)tVCccU~z51n_7 zzwUrbEQ6cD%EHkMvy`wY8@u8+;w?8@JzDFL$PM=d6A+f6) z1k@*x^Xha&icgdbTW-4vO^Z8Z3eNqo8s(`lNQ*{o8 z9v(=9+%%JA8(1Q&i{3hh`ghew5i|d?r0LH8*$K7!fY6J>(0FxejA1y@&q8-eC_VbUP(AbpYYx zbu~&5lHYTs{Ap;5<)|00jUf?L5UnOVn^9CW_22koOTnf8E{BXdOV~1{+EOE#^j?5X z>2xCKhnX1?<(9>NzLHk(JwJZ~1P%443tn>fm{Eab{a;xO5uwZyYD?2wpcCI2MWakF zG#zJKP*Re!xH<|{MgZ8lP!sddc|c*wpT~=}dKz(R1@^Kk5I(+pgeIZj1p-wJx5vK! zlGptgt^~st(gfpb`XH%u!Y!Gl;}xT0Rw}QknDb9VGBS+D3pH@b*uU~vjQ|W4A7DtB zb`!-F)SI`h69D98XXGPY#3C<9hjb&cW~J1C5(YS7c&%d#mgMd!b@Pq&I;+=sKq|In z00(z!{L$cl0bah7(E9gEo$pQvI2ut+YT-CVdRp0^F?wrU0?}o(OvZE&URzjL&_H}r zz6q8Fw@DlTggv{;Aw{t2w%<)y7od+z-)Fiye3)q1d+ z;}T7w1N|Z;=DJm=2KUaG!`j;A2HDQ?g_5ChNgyn&i5Y+YY}SMZkACUEI02LZr-@<*n5c4 z$9+lWS3kYletP=Z0{M`Q+L(-fwxp|;@5gDY$%A~a@(ir;3#ez*?MQ1=YDB9Vci-l~ z^zbJUS~(AH_}JJVD@eE_zvpXDtWoHi$FUvOWPJ%#6|YU80ppt{?O>n!AT+zA=iPVJ zH*i7BI}&!xEq$}5@(VJE7yb^0%q{|^SLOs$)4ER5AYcwSi+zv!?Z)`jvU~fU^27^+ zsm1*dO5O$9o+Is;2dAd!5I&ad0Md`huceRTz*EGFR9vac1oh#|>}kO6D`$irfMG$k z^~=Ncn4-J936#0oe8WfKj&FV=1C(@r0UZlB|K;swvN*su4;HoyqfFU*Xyxf?F@J+o z5G^7;I7n4^3ojm`8ui~0MLuOn7YHET<(5zUr7-jF5`+qy%fW$=omS_Bd-HH`G^MQzZ=U~`qt_%n)xK_=L^!?61 z-i~`S3>x!UXBU1oRDO2N+Dl^6`W$)4N-q-zhss8o6q}wT8a1d8`#sGxt??=T;G0JC@>MVOtgQAoEf?dhkx@vF$!hUP$D}u@P_Ng)gn5XV9dw^o2Fn&n)cQ9NflsL>(A(6?E*4t9Li8=uRVX zCyE?r)R5P%id&yTGhewjcb}X2oe%H0f|+0WLMnDB1pNS_i0^5yxp8Imw>0_J<_)4{ zK@4Wj4JdQpg4=Q>1W!p+>*;)3`;DCoQ6Q8%c3tB@-u5+Rwp)uCx%xUK%tncaV^-&K zkJvos%EY*^#}EPvxLZNNeBz(AsX6&`Z=|bozh!G>cuke$Y$J6m#Zn+ zo#i;iK?TD8(AIWjbA5lH7tAm6Om#dz9;l1txwiJ|zj#ho*^$0Rq_~wVBQLT2VwnY8 z*9x>Eb|Fa*jU5A=cUTb1(>2JLAsHi5t>eKtNQ9@sC@#*jqsLyWS0K9<@0IM%P3s>L zc}f`I*b0BIy4i%eHm(FwOSpdU40M|gTxuD_PEJnG^FqEb{^pIv2n)Q+i8Kmv!pq@JJAkq9Xj=19G2`>3oG3^D zBrt-8+`njNWZy;}=9ieGXE1YglHY-BjFbJc1VkMg>rg8w7pxToKku`g&(h8%HxC)4 z4vR&OGL&1tG10Cemb{{}7FPmkp6=)YcZ- zMSMyp?8g0mtKQ|?qHAzYM%X+w&5B(A<9}A9iU(!@2h5Uw8nz3hIzZTkUf}&MzY+_Q zT6gUl#k1-n^RZeIZ?GT}@2dFye4Lv2jUbyXsPD}aBd@+9RBap?pKb@CPf-XZ&5RDh z!!OYuZxTTfGh;@q$kmUp``MqE8My(S31qvig2)@>ZD`V-E}lyC!$YWw*zZhRj=Xxj z?QV*G?QC#nmldEOc!i7X>obU{=;KO(4us+WAZ|YDExqndZOGDas~k@MP2@igp|a9* zH9D(Q7%ji|xg7Z_(SbLL*?mck9H}nOn(NI@0PnPM{|xS}lN!!_h#QLsO{BQ!s~+lm z!ZH8X{@@O}YNP+yOd!Mf2{|DT`@AY*+-kE}PB<@&XCDY~-10v*UBxg$l;a?arY~QL zTWbYMH&)|Z3dtnx-e#-;Xgt1~73m4Uy))?{%jG#9Mw5}PpZ|{5)}S%bRsdQCiY0Q* z9-CigprG7a=pOrti6g;MbrE*|s7LBP(Ej|%y^5?})0Wc_5Iks12N^{`>AiK3ncs6C zUsacVye*ASPE*{>&9O0#+eVY*%aRAKMxhn6ra|$~FRaA@$Q9_U?(NV-5=~f%DFe*O zbNXEy&?*(-EbkvH4= zvxs69{T*2^$w8}`sw8wCo3Gwngw&4Nvc_r}F^juiWxtsp9TiVYCKY^4_W19CW5`T) zXP&ty9*(cnyuhggGp^A%YZ!96J>}hd#G4FLl~agTC(626{k8-hz-?j0C=&d;6Y$g3 zxW{$StxXC}rdaBznS0)TJ#VCkA9~{}UzPFIjVRgYJm#K^czOf>6N>o@Gmh{2Zz9jv z{$)y7$SYAq0>&0py}iAI`mD`H z<#dZGaHNh$&J}fKaYu_Ao*K)UzYBcCEcnmP9Wb^V#CS}&aQpWBaCK1RbPg22Fn;ic z?UqciTAzKX*Xu7RePHyJvd9c+IRy?d*s6^Qpeh1m6F7z5oY0LtxHNuK1D?_+eI$FPX*is;qL`CdAWfG@x~7*izCMc zmjljNxEsJi&u{&D;NQQOLnjd&E&ttshKx+Q?o9H|1&u|Z57e%2Pg=dC@!4UPJVRc3 zCNcsU0HMg4p$v&KQ2nW0%T}$guLlqq>zX8e zLiP2*v^-2JWXmCJ(H|mGLc~5`B8G8t*j}Q3F!hfmN_1}ca-voVK3AFv4r_gfeXN#=ZtPjM?4r-(xR>L@&MjYicF)RygNHJbq25- zNc{`|2iUIVOA&Bp0`cHF+V_ck(636^SnvL&neiW}Jy6{lMjE0N18E8GtAkEZp5*WT zqg!jM4gOkMK4~n$2c>CwS=qP$;e_*F0|h{3Y4d5dRp(j;rTgdReE_+20YXTiZ&O1p zjodL&e0_j($q4X9yW*#i+y6IPAYYJiMgHgi%4qf^_0NmQnmbhgX7=+xGyv)867Zu{ z#}EIXjt4$E|FzS`jUY>N`SZ+J;wB~x0HAD3^6+6F0@WL9#YM2++!+*{T2Ih6Q4c?) z+W}$&52cmR&^JT!?2?4x9n@a?I)FFv^$w>U3x2JwDTOXRO}VSSe`p4%F^M? zXi^QCFht!Y_aKAK|P-bc@tor z1RZYa2k;N=YRi~Q*b|f(=3jtgI9Z~5+D@#Dz9t`VCE|0?K@NtLvP5?vHnIKpV@*%B zmvmg1hYDwV(A%P6OHjk|0g{csIy*6SG*Hf+&wo)lq=^viIM347*H^r;fi&w zLu4!71RHoCkEhDGY+0mw1oLLF$;v>%uL!`UfS~psl=FagA@DYOXzB2S#DhT;B$2RoJ#>74J0J?8q)OL;dXUOfI)wKaSX_L0RFSR=4(_ntyyLOi;s5&LXAK_ zAr4L8kWvneQhvKQkQhkmqs7ic%4cGf;TB0{gxjpH0~{{@=JN?LPa;T9P5?Ck7}$|z3l z;(W29=OCFv^I`u7NT9?rO9Lk;V9+(6hBagyoXWr@Fw#rRti)({n5(9$DqcI3e5W4( zO5{vw`0XDyK8pVN^QX_{>|*5LL*>~OGq;;75AZ^V;m0fAkpz6Yov&XX5KYV3=I~?C z9!4tMu>nGr;9Ff(44saC=K=xAPr?9q^3ij1JTQ#8?j1hEZXNtHCmRBYt?K~$s-^Er zI!p4lYnXi#{bDUTDe08~1IWp-q(2mLW9Q_wN9vs_a(0LNXR~;!(LP~i)u}MX5YRz< z2oZK#!4mMLn+Mt&JODW06hBW3_Uqi48DIe?l+?6PtjRV{)rml%uW?WN@ix5xrgwME zVo_a3M~9Y>vgYN@0~*i+5C{|$hi%oCDyD56x`FMLiLvq21d7DX9Xo*O28?&qo2?hX z(F=@f!`q8V$klH`D0o?~0BjR8?BhVBWG-EEGO3uoXENmzxRK)t$g=y;+e8TeH$8~f3UPLb)lJkvdgen ze`Gf~?~5XndUpli@_1_;U_r1}2x(-M^eaufkv3~Uo&&j-qZm@JXe~6lbDckr7)-ae zv3dUNSy$X|AilsXa|YB{9_e$@qwGvom;f`w4a~!>sTL>{D6F&+R1@bZ!RWcd!Z{m4 zg=v78s^pBHwu0;eT^WEq0^yI?tOP(Q26zILl-SuMS}YDq`wf_VrUo~7qhO@S2|dlr z^r!l?jhr@MebfE0MEX_M4at-TgMVvlYbw`Q><-08OSN2ug0eCICS9o?_WaR^B4&WQ zaxcJ~St#wa2>xX4&GtHTIS)r%kaWujwXOas{rCXjmr(~A}*=8d$0 z{_WK|L{15RrAz6;?@F$A+O}FB%t`<{@jW&HKS6HV;rsCfaI-`J0Ad_Osz|d$(CdV~ zQQ&R$d(k%~6RjVL7%@49hlf|VFgDShcw28x0d+N2OVZSo5lp}_>l#|DBiU5FShrSA zoyD;+Ndt0p#r#xw4tg$9{+Pp_gUkpyVam(PfuARUHX`NNdyxyW85dHis=dU$1j={Rd$jpqnrn?G~*1+~t*?=UQ4d9nUe0MPYg^9b^ep(1P zEOm8tfi4z(w}j#$y0W$w+xE3JJVU|o3LF;KZS=VzvmtJlPtLpd0 zOYHLVTDrQIf5N$9$sc*3=`^@l`R2JDHY31T!?c9}E#79p?O-gZg0pJ8dey__a}bV# zSG+rkVP&D%*I?(EPL7LlCf{aV6Pq9{CWdN-xgG9LKZx#Rt4xWyKuE=J&*;b)?d9tP zp#c(QoA^$&GIf}ghacXw_=s4cpV9P#bH|JbVb44>I96?ffv_QsgA^+S0p8|oy1&Vm zbeF%d{r_wf`Tyr1Ql5krgUYARt9$4nZ9huCS=Q)8E>Dp<$6x z5~)rd5fK4Sk|P#{J|KbLy$W=eY{qOo3NKi@dwk_{4-Lz>}`L2-L?!!NNm!E~+F+2Y}Vv1LHvp z$Yml}V09l7gy=J2-I*4z1xvBZ@1H>{tgBbIJM*~j)HRYY_ni^G!W-=9XyWbuzGVK= zB+)_Y!9*F>9|N)}X`~9M{IGJ^tETB?Klw+CyG?5UEN6wc{{}AZD$lKPGW|7Favw^C z8U4gzzfXhxHT2%|-!V+ev2)i0_3I1G*L^!m%-3DFTKd1|PItVNyWEwar!MYwQ#uzG zemFd7wc~twX$FucMP=m{w;f6h_{<7Gq5%;Hmd}$+pY(^r3oS;F_FZme#=~eS+iu;vq=_GZRy(!FNw^sG1Q>B7|H; z|8boe%vZ?*a4yM^bVW-T*fcsHdG&Qgdt7YhZHA8N3w@? zqdL2k?9iOSFd_(oG1)v+U(1l&4)D8$PfSbzB?&b>o4{>=@8SVs+3Vn3^rp^U-@`)y zqjQEnHX%XAkC!`S+n&>~S%I6|4ylzjlVWUN-x_$b4`PE+dYoN3P0cTQONF51gXFc; z+GRa}xjkNQ$Q=665k|@&9g!X|(wP2z_#ARPrsi_zD6WSmL^I0-h-MG~{9B4`){qz$gG5Q9$I8?W&c ze|Q2xOc41>ozvlEZLWX=`7Mw|8|p4IY-uhnEzQUf<3vLV

4JF~){AHgEt@PfJdQ z^NpDR89{)|M5?H(z2B*s1VvV+gdhT`9g<=wc;N}0`bmPWg()_PCs*+&qy3;QRh5wvlsnsg1;Y0Juj0xJ-;fpg)P zS*)<1m>fgWHk-I%GhBwvDZplyIp!BJFYCwc|(Kp89&pr=3?F;5O>8VGQm z?Y9w*GC4Xr&O~pG<|*gBcn>0mwN3@A0zSrY724`%6}|`0J0mFKfQVv+5OoX_P(7=; z4yW$Yw{q@#4(_zg_ZEz`#qFnN4KQJH4>6UU2|*!@;VX9qM^C6LiomvGVZwm|Xb)RR0djzOo(M za=N1KIzV}Rjmn9v9p|y#n~}^A)$lJY-4d(Cud#H;lzyw+L?%P{i#VvA2wGcZse7bz zSTMtg=KVRw81y z55yQ3Ec8-GTdgK=CAza(#EFV>Hd$%HC4Mp`&7WY;J#Q&Mjbju(L#Xb`AngAxqd|uE zwbI6GW;!ebI1#|x2y@@1^D55LRp8fA3KXBpbX&ofc<(Xs610NAngAqX>$d|WEgg7% z0YxD233Wk^TkxT$&ip6Rmkj}v1AJ&lkYfRS{)bD*J5(<930a{&V%mu;%&0%v+-p@2 z^A8K(mn8P@##VtFiZksR`j}k)`OdTKy23Aa`-EJqPfcATzQfOTy+KJE2PbH|as4?} zyv{Tn`g*Y;zyB^)YuzocFNm3)Wj$LYDGR*~WpFU^w-&wRN7-v?`&2_UU) z)8v)Ge#}(5G}%qKnA^pY8$k{HS1ZTV7o9c8wAZ6xEjLjEo&p##w9B^yy{c(aW`>Q= zO_^D2`=3{gE*q4K3skAFM5_(U3w+Z!cM3|u>MH%zlSsb|&67{hcnrTHM)3D)0Yo?vbl;n9Wa}sXPM(TO&d$#F@DJ1Y%X4Bl9 zEGkoQ3YZNmU~_4Q*(qVx!{jxMf!j&<(07GZU5PpYVwz#2hLQA@WoDa!x&s zjI}NwRi9}qVFdEOo!c^P(V$w;n=Aa>Re;KDd=cO#uqyvmnFxjUp=Wc+=6UnNAzbg& zsy>7FoFz>o8|LEeeew$8?Ku^dalUlVOrq@1^?uE6WF;nDPcZ8$ z*|o{E(S4TaODgu2yGc%T~-z@lEsYUVm%kW35u??kDhuV6c5Q_O>%&a-7e@ zZSH=f)4u)7Z*D<9^3W`G!PClpSJM7{U~ zuFM{3!c!26aK!vYi=nZVNqoaMF|Mx{^Pd*U*BoQl>a^i=VUrY^IXko!A!N>0oC9wO zR|mz#^TRKtBRM5|p^C*l%ICHo;%a8VbWA{kKid{M(#OVcHhGq0#7zD|$dk(w9&V=y z-Pt&LSVXWt5?3}gVcxs@Sa7_as=B4D^wQ;by=Br?7j8ZQX_WHRu?VhYhWd1?;B@ebuy701fFrn$MFLe#e{X_0N>pr{ZZ2FX= z?c2NXuOuqC93wXiYEROo9?bNx_c{qW5wR`wG#|dekujRgQ zEN-eNFPn0rX5L;2Y`SCNJ>@U?D~Y=}0cZ3KGsJ%1YMne7YS{EraZ{ct8_Ar}55DW1 zbYXywx(yRb}q@S9Cdey);D%_mmNcagVH_z#Qq z9)CsM#d3gg-jLoS|3!?3?X|`wO?b6e;WeivoXWGsP-fivRGH|r6-D1cTmlm(uhx5> zGT0kZW%K9E1ZE23QI8kAxLTqpwj*vhFIX_KQE1z5oFuLW2i|C*!6#m2jCpM0m~4j0 z)>EkRg=lPi+{?)uKfsloxb&N-H;%0%JENzlRFnPAryCF7+cch*Lxn8#q{WbmH3@I@ z<1Q0@+V*%~(#(SgzwMkaJONGpI-&f8J=aELQwa1gUj%T=G?<4Tf zRetp>nNO-OI9jGi{Jy^?YwX6*Bx^h_(}d3N^THaom%q=xC{Y^4coUo0H!;w26#8M5 z;L&}~r=22@b6eK4Ci_3E0*mYpxBK&yo+A0j2%6XHV`UK;&X>CN^@-VEMlmY64gKD8 z9d|OjR%|+FC^ElC)2O8g)h($)?Am3G!d_qg_|~GiJGl-ycrbIGY`ku__9`lumO7O3 zedi~M1jL%ygu2D^*0p!dYHTf0tG~P?3Bbl%1Xdv!HtU}4MnmgoysV;GMW3F4WnfS2 zeT`P)0MoRambla7Fx^D^V{N2p-aIc`MHXoz|P4@{868YwNr1A{%)9-u4d<Av-BRV@FA5QPn zkt(eHHm|?W^X)k5Vsb!~Kd5D3;O3zjP7n*T>1dMBEA9uq59ojzgpK}o@RoCBEAPG~ zdz%;Z>E2$kh>bl_{-+(zPhWm5{81l-=juO3B#*ll!LQT53_`|))A&j4sTfAglco@} zW6&~l>KVO&sFI7)Y*_={RMbo;&&q)K^+6{mp`&zm%-6E2`ic0tT?4=V;yJDzBcd$3 zU+z;~5#JWm`}z4v`f0-NhqRq5dEI)uh_p-1#P9tgL}^WLzs#=5pz~Ka~XOi4?* zuPnBInfoExopPha#4MubzE$9gZP?{tJyYhx4Z+Euzm%0GMA6I5nByB)=gL0P430TN z5iwgcBiG~y0={dFMXd6l8Ux5XDw=unNL~4BpU#H97g%*}D}On*5Y8U<`lw|?$JUgQ z@hjxQy{+!gM^4XliLy;6m3?W1rIlu?Roa@VPAk%{7T+r-GngceVFd z;0*^wD5u&T}hAi?jhle+;C+-MAk?lEd?d_~PVO7Un-(KPw4UATH z)XcBt-96HQe)#S2s_72Z=3|mk!TN~?1;OBeL`og8*DcRP4M#4z;0hf0)R5PAX_Cj^ zACQHAtf!a;lNkktyMGRVl$3s49G*FQNoIx$uynLhGmyk+{CRjipK{MZu9^NO;-cE4 z=1UHPTWQqI&5C8vjgQ_lsi`laCf)ko2`q`CeX;Yg=Pi=A-bcfdmvf@#S_v5kdUjTi8825)@1xLuS!GsG zir1e^c4vxQPlP|Sn};k=ag5IObIH%X!W?7LK9q}KWgzaa`QSdBMf1Xuee(BK2iua2 z5~ehX%OwaeRkl^b+^{kP1gZp(&I zA-NSQs_l?>`?2dBuV3z|y*}v=ZBg;AYKUDS4g&k=}t4KX>NJ>Qx=ZQR?usy zn|H0u#M9-CC9ti)vtF!1Bt)|tx^TWq{YX5BNZKgm@lTDVpWMDv7cCn|%_(8G@&vOV zNNof_Y&Q5BI!fsmO?*Ei#)7mFN2HQzb!NNwpF`b-gp)pD(QtGq_>?AhYW$gRsO-Xk z4xzrzx%qMUG%|8;e7(rDp73+Pp8}Nip^(f8in@ZgLArNiW)<czLkwf3dEKRTDJc(irwIDB>$Q~NbMB3f5-MjCe9-4Q+LlhAIE&SO zE|I1qxBMu#Wx*q}jm z^<$?&Czq`AT4Nc-TI%Jl)OsP8*H?Jg(`5G~CDBSrvoX;lnyC>(L4=V(O256_5X~{X z4Gl^%^kUSYV`_wR@vWyij3GF1RP(Fm`j<&g33{Dusl*`NGaQoU2SQhcb?Siz2YbQA zPIAWs`G^H#mi%zS12J(^!M&Y`+8U+sujj$WjACJ4)dz2 zgeVvj+IKOGqh-{>x`e16{wY$eA^oG=xLVWu6h`*(H*H59!^Z>SV`|PQA-f2;@qHRK z)rgZP?)1{;JW4F{iTr~hzGETz>sHU>MFvhQePM1ifeXo#4wh|dJteFokD6vE3RJvE zKJ==7oIUM$Kk69M|DsFOVBo|-#knJ4V64GM1=lxksl}|@bTg|b$t$tn=CUqiJXJLf z8m0ev^j_6Ihdj!?=%r?}0)DX%y+lWiVkaE(WDSEM=9MiaripU4{Y)Zq>4dOdl!+av zG7KSfTw?835%0P|bLLWAsWxsqx;NH=1!POpqm*v)bB9xhKzBhjP z)Z!Q*>_=x@%BRkppzZx9O5#b3Jx~iY% z-|exY-eN@W(L$ydxN0o5=ki_9cex|%Ip>WVhMfdSeuD1HE{ThOv@800e148;9p$L5 z+H~RhDka3(}f{ zuW*0d HkMn;36z3XH literal 0 HcmV?d00001 diff --git a/src/WebExpress.Test/test/contentTypeMultipartFormData3.post b/src/WebExpress.Test/test/contentTypeMultipartFormData3.post new file mode 100644 index 0000000..53cee69 --- /dev/null +++ b/src/WebExpress.Test/test/contentTypeMultipartFormData3.post @@ -0,0 +1,23 @@ +POST /ix/manufacturers/f9b28160-42d6-4b76-8c61-1f591780576a/media HTTP/1.1 +Host: localhost +Connection: keep-alive +Content-Length: 142 +Cache-Control: max-age=0 +Upgrade-Insecure-Requests: 1 +Origin: http://localhost +Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryLutwupO79tSZKWVN +User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36 Edg/87.0.664.66 +Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 +Sec-Fetch-Site: same-origin +Sec-Fetch-Mode: navigate +Sec-Fetch-User: ?1 +Sec-Fetch-Dest: document +Referer: http://localhost/ix/manufacturers/f9b28160-42d6-4b76-8c61-1f591780576a/media +Accept-Encoding: gzip, deflate, br +Accept-Language: de,en;q=0.9,en-GB;q=0.8,de-DE;q=0.7,en-US;q=0.6 + +------WebKitFormBoundaryLutwupO79tSZKWVN +Content-Disposition: form-data; name="submit_del" + +1 +------WebKitFormBoundaryLutwupO79tSZKWVN-- diff --git a/src/WebExpress.Test/test/contentTypeMultipartFormData4.post b/src/WebExpress.Test/test/contentTypeMultipartFormData4.post new file mode 100644 index 0000000..d979a39 --- /dev/null +++ b/src/WebExpress.Test/test/contentTypeMultipartFormData4.post @@ -0,0 +1,83 @@ +POST /ix/manufacturers/f9b28160-42d6-4b76-8c61-1f591780576a/media HTTP/1.1 +Host: localhost +Connection: keep-alive +Content-Length: 1940 +Cache-Control: max-age=0 +Upgrade-Insecure-Requests: 1 +Origin: http://localhost +Content-Type: multipart/form-data; boundary=----WebKitFormBoundarynEaBs46wK8BWblOG +User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36 Edg/87.0.664.66 +Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 +Sec-Fetch-Site: same-origin +Sec-Fetch-Mode: navigate +Sec-Fetch-User: ?1 +Sec-Fetch-Dest: document +Referer: http://localhost/ix/manufacturers/f9b28160-42d6-4b76-8c61-1f591780576a/media +Accept-Encoding: gzip, deflate, br +Accept-Language: de,en;q=0.9,en-GB;q=0.8,de-DE;q=0.7,en-US;q=0.6 + +------WebKitFormBoundarynEaBs46wK8BWblOG +Content-Disposition: form-data; name="name" + +121 PD Plus +------WebKitFormBoundarynEaBs46wK8BWblOG +Content-Disposition: form-data; name="selection-manufacturer" + +1913467d-037c-4d13-bf4a-50a2e1496da8 +------WebKitFormBoundarynEaBs46wK8BWblOG +Content-Disposition: form-data; name="selection-location" + +b725cb6a-f3b7-4164-ac90-51f2ef09b2b5 +------WebKitFormBoundarynEaBs46wK8BWblOG +Content-Disposition: form-data; name="selection-supplier" + + +------WebKitFormBoundarynEaBs46wK8BWblOG +Content-Disposition: form-data; name="selection-ledgeraccount" + +2ca06694-84e0-410d-997a-f71c5c95336b +------WebKitFormBoundarynEaBs46wK8BWblOG +Content-Disposition: form-data; name="selection-costcenter" + + +------WebKitFormBoundarynEaBs46wK8BWblOG +Content-Disposition: form-data; name="selection-condition" + + +------WebKitFormBoundarynEaBs46wK8BWblOG +Content-Disposition: form-data; name="selection-parent" + + +------WebKitFormBoundarynEaBs46wK8BWblOG +Content-Disposition: form-data; name="selection-template" + +c827d356-7ea4-4bbd-86f2-127fa786a279 +------WebKitFormBoundarynEaBs46wK8BWblOG +Content-Disposition: form-data; name="attribute_184cc9de-91ed-4bba-b10f-07225c33fcfd" + + +------WebKitFormBoundarynEaBs46wK8BWblOG +Content-Disposition: form-data; name="attribute_dbdab181-bd64-4328-98e6-7ff2f47b3842" + + +------WebKitFormBoundarynEaBs46wK8BWblOG +Content-Disposition: form-data; name="costvalue" + +10 +------WebKitFormBoundarynEaBs46wK8BWblOG +Content-Disposition: form-data; name="purchasedate" + +01.05.2017 +------WebKitFormBoundarynEaBs46wK8BWblOG +Content-Disposition: form-data; name="derecognitiondate" + + +------WebKitFormBoundarynEaBs46wK8BWblOG +Content-Disposition: form-data; name="description" + +SN: 80093116 +------WebKitFormBoundarynEaBs46wK8BWblOG +Content-Disposition: form-data; name="submit-formular-inventory" + +1 +------WebKitFormBoundarynEaBs46wK8BWblOG-- diff --git a/src/WebExpress.Test/test/contentTypeMultipartFormData_Umlaut.post b/src/WebExpress.Test/test/contentTypeMultipartFormData_Umlaut.post new file mode 100644 index 0000000..1d5d589 --- /dev/null +++ b/src/WebExpress.Test/test/contentTypeMultipartFormData_Umlaut.post @@ -0,0 +1,31 @@ +POST /ix/manufactors/67d35a0f-7e94-4bfd-a309-36e9162a67ff HTTP/1.1 +Host: localhost +Connection: keep-alive +Content-Length: 322 +Cache-Control: max-age=0 +Upgrade-Insecure-Requests: 1 +User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36 Edg/87.0.664.60 +Origin: null +Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryx68be6Cm22xpQciB +Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 +Sec-Fetch-Site: cross-site +Sec-Fetch-Mode: navigate +Sec-Fetch-User: ?1 +Sec-Fetch-Dest: document +Referer: http://localhost/ix/manufactors/67d35a0f-7e94-4bfd-a309-36e9162a67ff +Accept-Encoding: gzip, deflate, br +Accept-Language: de,en;q=0.9,en-GB;q=0.8,de-DE;q=0.7,en-US;q=0.6 + +------WebKitFormBoundaryx68be6Cm22xpQciB +Content-Disposition: form-data; name="a" + +ä +------WebKitFormBoundaryx68be6Cm22xpQciB +Content-Disposition: form-data; name="b" + +ö ü +------WebKitFormBoundaryx68be6Cm22xpQciB +Content-Disposition: form-data; name="submit_" + +1 +------WebKitFormBoundaryx68be6Cm22xpQciB-- diff --git a/src/WebExpress.Test/test/contentTypeTextPlain.post b/src/WebExpress.Test/test/contentTypeTextPlain.post new file mode 100644 index 0000000..b534298 --- /dev/null +++ b/src/WebExpress.Test/test/contentTypeTextPlain.post @@ -0,0 +1,31 @@ +POST /ix/manufactors/67d35a0f-7e94-4bfd-a309-36e9162a67ff HTTP/1.1 +Host: localhost +Connection: keep-alive +Content-Length: 1866 +Cache-Control: max-age=0 +Upgrade-Insecure-Requests: 1 +Origin: http://localhost +Content-Type: text/plain +User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36 Edg/87.0.664.60 +Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 +Sec-Fetch-Site: same-origin +Sec-Fetch-Mode: navigate +Sec-Fetch-User: ?1 +Sec-Fetch-Dest: document +Referer: http://localhost/ix/manufactors/67d35a0f-7e94-4bfd-a309-36e9162a67ff +Accept-Encoding: gzip, deflate, br +Accept-Language: de,en;q=0.9,en-GB;q=0.8,de-DE;q=0.7,en-US;q=0.6 + +name=ROBOTRON +description=Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et +accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. + + +Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. + + +Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. + +image=Unbenannt.png +tag= +submit_manufactor=1 diff --git a/src/WebExpress.Test/test/contentTypeTextPlain_Umlaut.post b/src/WebExpress.Test/test/contentTypeTextPlain_Umlaut.post new file mode 100644 index 0000000..cf7e6c3 --- /dev/null +++ b/src/WebExpress.Test/test/contentTypeTextPlain_Umlaut.post @@ -0,0 +1,21 @@ +POST /ix/manufactors/67d35a0f-7e94-4bfd-a309-36e9162a67ff HTTP/1.1 +Host: localhost +Connection: keep-alive +Content-Length: 26 +Cache-Control: max-age=0 +Upgrade-Insecure-Requests: 1 +Origin: http://localhost +Content-Type: text/plain +User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36 Edg/87.0.664.60 +Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 +Sec-Fetch-Site: same-origin +Sec-Fetch-Mode: navigate +Sec-Fetch-User: ?1 +Sec-Fetch-Dest: document +Referer: http://localhost/ix/manufactors/67d35a0f-7e94-4bfd-a309-36e9162a67ff +Accept-Encoding: gzip, deflate, br +Accept-Language: de,en;q=0.9,en-GB;q=0.8,de-DE;q=0.7,en-US;q=0.6 + +a=ä +b=ö ü +submit_=1 diff --git a/src/WebExpress.Test/test/contentTypeXwwwFormUrlencoded.post b/src/WebExpress.Test/test/contentTypeXwwwFormUrlencoded.post new file mode 100644 index 0000000..5a5b43d --- /dev/null +++ b/src/WebExpress.Test/test/contentTypeXwwwFormUrlencoded.post @@ -0,0 +1,19 @@ +POST /ix/manufactors/67d35a0f-7e94-4bfd-a309-36e9162a67ff HTTP/1.1 +Host: localhost +Connection: keep-alive +Content-Length: 1936 +Cache-Control: max-age=0 +Upgrade-Insecure-Requests: 1 +Origin: http://localhost +Content-Type: application/x-www-form-urlencoded +User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36 Edg/87.0.664.60 +Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 +Sec-Fetch-Site: same-origin +Sec-Fetch-Mode: navigate +Sec-Fetch-User: ?1 +Sec-Fetch-Dest: document +Referer: http://localhost/ix/manufactors/67d35a0f-7e94-4bfd-a309-36e9162a67ff +Accept-Encoding: gzip, deflate, br +Accept-Language: de,en;q=0.9,en-GB;q=0.8,de-DE;q=0.7,en-US;q=0.6 + +name=ROBOTRON&description=Lorem+ipsum+dolor+sit+amet%2C+consetetur+sadipscing+elitr%2C+sed+diam+nonumy+eirmod+tempor+invidunt+ut+labore+et+dolore+magna+aliquyam+erat%2C+sed+diam+voluptua.+At+vero+eos+et+accusam+et+justo+duo+dolores+et+ea+rebum.+Stet+clita+kasd+gubergren%2C+no+sea+takimata+sanctus+est+Lorem+ipsum+dolor+sit+amet.+Lorem+ipsum+dolor+sit+amet%2C+consetetur+sadipscing+elitr%2C+sed+diam+nonumy+eirmod+tempor+invidunt+ut+labore+et+dolore+magna+aliquyam+erat%2C+sed+diam+voluptua.+At+vero+eos+et+accusam+et+justo+duo+dolores+et+ea+rebum.+Stet+clita+kasd+gubergren%2C+no+sea+takimata+sanctus+est+Lorem+ipsum+dolor+sit+amet.%0D%0A%0D%0A%0D%0ALorem+ipsum+dolor+sit+amet%2C+consetetur+sadipscing+elitr%2C+sed+diam+nonumy+eirmod+tempor+invidunt+ut+labore+et+dolore+magna+aliquyam+erat%2C+sed+diam+voluptua.+At+vero+eos+et+accusam+et+justo+duo+dolores+et+ea+rebum.+Stet+clita+kasd+gubergren%2C+no+sea+takimata+sanctus+est+Lorem+ipsum+dolor+sit+amet.+Lorem+ipsum+dolor+sit+amet%2C+consetetur+sadipscing+elitr%2C+sed+diam+nonumy+eirmod+tempor+invidunt+ut+labore+et+dolore+magna+aliquyam+erat%2C+sed+diam+voluptua.+At+vero+eos+et+accusam+et+justo+duo+dolores+et+ea+rebum.+Stet+clita+kasd+gubergren%2C+no+sea+takimata+sanctus+est+Lorem+ipsum+dolor+sit+amet.%0D%0A%0D%0A%0D%0ALorem+ipsum+dolor+sit+amet%2C+consetetur+sadipscing+elitr%2C+sed+diam+nonumy+eirmod+tempor+invidunt+ut+labore+et+dolore+magna+aliquyam+erat%2C+sed+diam+voluptua.+At+vero+eos+et+accusam+et+justo+duo+dolores+et+ea+rebum.+Stet+clita+kasd+gubergren%2C+no+sea+takimata+sanctus+est+Lorem+ipsum+dolor+sit+amet.+Lorem+ipsum+dolor+sit+amet%2C+consetetur+sadipscing+elitr%2C+sed+diam+nonumy+eirmod+tempor+invidunt+ut+labore+et+dolore+magna+aliquyam+erat%2C+sed+diam+voluptua.+At+vero+eos+et+accusam+et+justo+duo+dolores+et+ea+rebum.+Stet+clita+kasd+gubergren%2C+no+sea+takimata+sanctus+est+Lorem+ipsum+dolor+sit+amet.%0D%0A&image=Unbenannt.png&tag=&submit_manufactor=1 diff --git a/src/WebExpress.Test/test/contentTypeXwwwFormUrlencoded_umlaut.post b/src/WebExpress.Test/test/contentTypeXwwwFormUrlencoded_umlaut.post new file mode 100644 index 0000000..e965b0b --- /dev/null +++ b/src/WebExpress.Test/test/contentTypeXwwwFormUrlencoded_umlaut.post @@ -0,0 +1,19 @@ +POST /ix/manufactors/67d35a0f-7e94-4bfd-a309-36e9162a67ff HTTP/1.1 +Host: localhost +Connection: keep-alive +Content-Length: 36 +Cache-Control: max-age=0 +Upgrade-Insecure-Requests: 1 +Origin: http://localhost +Content-Type: application/x-www-form-urlencoded +User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36 Edg/87.0.664.60 +Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 +Sec-Fetch-Site: same-origin +Sec-Fetch-Mode: navigate +Sec-Fetch-User: ?1 +Sec-Fetch-Dest: document +Referer: http://localhost/ix/manufactors/67d35a0f-7e94-4bfd-a309-36e9162a67ff +Accept-Encoding: gzip, deflate, br +Accept-Language: de,en;q=0.9,en-GB;q=0.8,de-DE;q=0.7,en-US;q=0.6 + +a=%C3%A4&b=%C3%B6+%C3%BC&submit_=1 diff --git a/src/WebExpress.Test/test/general.get b/src/WebExpress.Test/test/general.get new file mode 100644 index 0000000..7d52120 --- /dev/null +++ b/src/WebExpress.Test/test/general.get @@ -0,0 +1,14 @@ +GET /abc/xyz/A7BCCCA9-4C7E-4117-9EE2-ECC3381B605A HTTP/1.1 +Host: localhost +Connection: keep-alive +Cache-Control: max-age=0 +Upgrade-Insecure-Requests: 1 +User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36 Edg/87.0.664.60 +Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 +Sec-Fetch-Site: cross-site +Sec-Fetch-Mode: navigate +Sec-Fetch-User: ?1 +Sec-Fetch-Dest: document +Accept-Encoding: gzip, deflate, br +Accept-Language: de,en;q=0.9,en-GB;q=0.8,de-DE;q=0.7,en-US;q=0.6 + diff --git a/src/WebExpress.Test/test/less.get b/src/WebExpress.Test/test/less.get new file mode 100644 index 0000000..f843c6e --- /dev/null +++ b/src/WebExpress.Test/test/less.get @@ -0,0 +1,2 @@ +GET /abc/xyz/A7BCCCA9-4C7E-4117-9EE2-ECC3381B605A HTTP/1.1 +Host: localhost diff --git a/src/WebExpress.Test/test/massive.get b/src/WebExpress.Test/test/massive.get new file mode 100644 index 0000000..ec786c3 --- /dev/null +++ b/src/WebExpress.Test/test/massive.get @@ -0,0 +1,113 @@ +GET /abc/xyz/A7BCCCA9-4C7E-4117-9EE2-ECC3381B605A HTTP/1.1 +Host: localhost +Connection: keep-alive +Cache-Control: max-age=0 +Upgrade-Insecure-Requests: 1 +User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36 Edg/87.0.664.60 +Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 +Sec-Fetch-Site: cross-site +Sec-Fetch-Mode: navigate +Sec-Fetch-User: ?1 +Sec-Fetch-Dest: document +Accept-Encoding: gzip, deflate, br +Accept-Language: de,en;q=0.9,en-GB;q=0.8,de-DE;q=0.7,en-US;q=0.6 +Field-1: abcdefghijklmnopqrstuvwxyz +Field-2: abcdefghijklmnopqrstuvwxyz +Field-3: abcdefghijklmnopqrstuvwxyz +Field-4: abcdefghijklmnopqrstuvwxyz +Field-5: abcdefghijklmnopqrstuvwxyz +Field-6: abcdefghijklmnopqrstuvwxyz +Field-7: abcdefghijklmnopqrstuvwxyz +Field-8: abcdefghijklmnopqrstuvwxyz +Field-9: abcdefghijklmnopqrstuvwxyz +Field-10: abcdefghijklmnopqrstuvwxyz +Field-11: abcdefghijklmnopqrstuvwxyz +Field-12: abcdefghijklmnopqrstuvwxyz +Field-13: abcdefghijklmnopqrstuvwxyz +Field-14: abcdefghijklmnopqrstuvwxyz +Field-15: abcdefghijklmnopqrstuvwxyz +Field-16: abcdefghijklmnopqrstuvwxyz +Field-17: abcdefghijklmnopqrstuvwxyz +Field-18: abcdefghijklmnopqrstuvwxyz +Field-19: abcdefghijklmnopqrstuvwxyz +Field-20: abcdefghijklmnopqrstuvwxyz +Field-21: abcdefghijklmnopqrstuvwxyz +Field-22: abcdefghijklmnopqrstuvwxyz +Field-23: abcdefghijklmnopqrstuvwxyz +Field-24: abcdefghijklmnopqrstuvwxyz +Field-25: abcdefghijklmnopqrstuvwxyz +Field-26: abcdefghijklmnopqrstuvwxyz +Field-27: abcdefghijklmnopqrstuvwxyz +Field-28: abcdefghijklmnopqrstuvwxyz +Field-29: abcdefghijklmnopqrstuvwxyz +Field-30: abcdefghijklmnopqrstuvwxyz +Field-31: abcdefghijklmnopqrstuvwxyz +Field-32: abcdefghijklmnopqrstuvwxyz +Field-33: abcdefghijklmnopqrstuvwxyz +Field-34: abcdefghijklmnopqrstuvwxyz +Field-35: abcdefghijklmnopqrstuvwxyz +Field-36: abcdefghijklmnopqrstuvwxyz +Field-37: abcdefghijklmnopqrstuvwxyz +Field-38: abcdefghijklmnopqrstuvwxyz +Field-39: abcdefghijklmnopqrstuvwxyz +Field-40: abcdefghijklmnopqrstuvwxyz +Field-41: abcdefghijklmnopqrstuvwxyz +Field-42: abcdefghijklmnopqrstuvwxyz +Field-43: abcdefghijklmnopqrstuvwxyz +Field-44: abcdefghijklmnopqrstuvwxyz +Field-45: abcdefghijklmnopqrstuvwxyz +Field-46: abcdefghijklmnopqrstuvwxyz +Field-47: abcdefghijklmnopqrstuvwxyz +Field-48: abcdefghijklmnopqrstuvwxyz +Field-49: abcdefghijklmnopqrstuvwxyz +Field-50: abcdefghijklmnopqrstuvwxyz +Field-51: abcdefghijklmnopqrstuvwxyz +Field-52: abcdefghijklmnopqrstuvwxyz +Field-53: abcdefghijklmnopqrstuvwxyz +Field-54: abcdefghijklmnopqrstuvwxyz +Field-55: abcdefghijklmnopqrstuvwxyz +Field-56: abcdefghijklmnopqrstuvwxyz +Field-57: abcdefghijklmnopqrstuvwxyz +Field-58: abcdefghijklmnopqrstuvwxyz +Field-59: abcdefghijklmnopqrstuvwxyz +Field-60: abcdefghijklmnopqrstuvwxyz +Field-61: abcdefghijklmnopqrstuvwxyz +Field-62: abcdefghijklmnopqrstuvwxyz +Field-63: abcdefghijklmnopqrstuvwxyz +Field-64: abcdefghijklmnopqrstuvwxyz +Field-65: abcdefghijklmnopqrstuvwxyz +Field-66: abcdefghijklmnopqrstuvwxyz +Field-67: abcdefghijklmnopqrstuvwxyz +Field-68: abcdefghijklmnopqrstuvwxyz +Field-69: abcdefghijklmnopqrstuvwxyz +Field-70: abcdefghijklmnopqrstuvwxyz +Field-71: abcdefghijklmnopqrstuvwxyz +Field-72: abcdefghijklmnopqrstuvwxyz +Field-73: abcdefghijklmnopqrstuvwxyz +Field-74: abcdefghijklmnopqrstuvwxyz +Field-75: abcdefghijklmnopqrstuvwxyz +Field-76: abcdefghijklmnopqrstuvwxyz +Field-77: abcdefghijklmnopqrstuvwxyz +Field-78: abcdefghijklmnopqrstuvwxyz +Field-79: abcdefghijklmnopqrstuvwxyz +Field-80: abcdefghijklmnopqrstuvwxyz +Field-81: abcdefghijklmnopqrstuvwxyz +Field-82: abcdefghijklmnopqrstuvwxyz +Field-83: abcdefghijklmnopqrstuvwxyz +Field-84: abcdefghijklmnopqrstuvwxyz +Field-85: abcdefghijklmnopqrstuvwxyz +Field-86: abcdefghijklmnopqrstuvwxyz +Field-87: abcdefghijklmnopqrstuvwxyz +Field-88: abcdefghijklmnopqrstuvwxyz +Field-89: abcdefghijklmnopqrstuvwxyz +Field-90: abcdefghijklmnopqrstuvwxyz +Field-91: abcdefghijklmnopqrstuvwxyz +Field-92: abcdefghijklmnopqrstuvwxyz +Field-93: abcdefghijklmnopqrstuvwxyz +Field-94: abcdefghijklmnopqrstuvwxyz +Field-95: abcdefghijklmnopqrstuvwxyz +Field-96: abcdefghijklmnopqrstuvwxyz +Field-97: abcdefghijklmnopqrstuvwxyz +Field-98: abcdefghijklmnopqrstuvwxyz +Field-99: abcdefghijklmnopqrstuvwxyz + diff --git a/src/WebExpress.Test/test/param.get b/src/WebExpress.Test/test/param.get new file mode 100644 index 0000000..078a594 --- /dev/null +++ b/src/WebExpress.Test/test/param.get @@ -0,0 +1,14 @@ +GET /abc/xyz/A7BCCCA9-4C7E-4117-9EE2-ECC3381B605A?a=1&b=1%202 HTTP/1.1 +Host: localhost +Connection: keep-alive +Cache-Control: max-age=0 +Upgrade-Insecure-Requests: 1 +User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36 Edg/87.0.664.60 +Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 +Sec-Fetch-Site: cross-site +Sec-Fetch-Mode: navigate +Sec-Fetch-User: ?1 +Sec-Fetch-Dest: document +Accept-Encoding: gzip, deflate, br +Accept-Language: de,en;q=0.9,en-GB;q=0.8,de-DE;q=0.7,en-US;q=0.6 + diff --git a/src/WebExpress.Test/test/param_umlaut.get b/src/WebExpress.Test/test/param_umlaut.get new file mode 100644 index 0000000..894e9c9 --- /dev/null +++ b/src/WebExpress.Test/test/param_umlaut.get @@ -0,0 +1,14 @@ +GET /abc/xyz/A7BCCCA9-4C7E-4117-9EE2-ECC3381B605A?a=%c3%a4&b=%c3%b6%20%c3%bc HTTP/1.1 +Host: localhost +Connection: keep-alive +Cache-Control: max-age=0 +Upgrade-Insecure-Requests: 1 +User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36 Edg/87.0.664.60 +Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 +Sec-Fetch-Site: cross-site +Sec-Fetch-Mode: navigate +Sec-Fetch-User: ?1 +Sec-Fetch-Dest: document +Accept-Encoding: gzip, deflate, br +Accept-Language: de,en;q=0.9,en-GB;q=0.8,de-DE;q=0.7,en-US;q=0.6 + diff --git a/src/WebExpress.sln b/src/WebExpress.sln new file mode 100644 index 0000000..f6601b8 --- /dev/null +++ b/src/WebExpress.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.7.34221.43 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebExpress", "WebExpress\WebExpress.csproj", "{FA4AD06F-77C5-410E-B03C-3FABF00E692D}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebExpress.Test", "WebExpress.Test\WebExpress.Test.csproj", "{135F043D-1881-4C4A-8120-5FE11498E1A4}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {FA4AD06F-77C5-410E-B03C-3FABF00E692D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FA4AD06F-77C5-410E-B03C-3FABF00E692D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FA4AD06F-77C5-410E-B03C-3FABF00E692D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FA4AD06F-77C5-410E-B03C-3FABF00E692D}.Release|Any CPU.Build.0 = Release|Any CPU + {135F043D-1881-4C4A-8120-5FE11498E1A4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {135F043D-1881-4C4A-8120-5FE11498E1A4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {135F043D-1881-4C4A-8120-5FE11498E1A4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {135F043D-1881-4C4A-8120-5FE11498E1A4}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {FAFE4739-CC63-4B24-8358-A6A8868C29F4} + EndGlobalSection +EndGlobal diff --git a/src/WebExpress/ArguemtParserResult.cs b/src/WebExpress/ArguemtParserResult.cs new file mode 100644 index 0000000..ad49af4 --- /dev/null +++ b/src/WebExpress/ArguemtParserResult.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; + +namespace WebExpress +{ + ///

+ /// List with the prepared program arguments. + /// + public class ArguemtParserResult : Dictionary + { + + } +} diff --git a/src/WebExpress/ArgumentParser.cs b/src/WebExpress/ArgumentParser.cs new file mode 100644 index 0000000..411482b --- /dev/null +++ b/src/WebExpress/ArgumentParser.cs @@ -0,0 +1,130 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace WebExpress +{ + /// + /// Parse the handoff arguments. + /// + public class ArgumentParser + { + /// + /// The singelton. + /// + private static ArgumentParser m_this = null; + + /// + /// Enumeration of all registered commands. + /// + private List Commands { get; set; } + + /// + /// Constructor + /// + public ArgumentParser() + { + Commands = new List(); + } + + /// + /// Registers a command. + /// + /// The command to register. + public void Register(ArgumentParserCommand command) + { + Commands.Add(command); + } + + /// + /// Prepare program arguments. + /// A program argument consists of a command and a value. The value can + /// contain the empty string, for example, -help. Commands beginning + /// with -- are considered comments and are not considered further. + /// + /// The program arguments. + /// A list of prepared program arguments. + public ArguemtParserResult Parse(string[] args) + { + var argsDict = new ArguemtParserResult(); + + var key = ""; + var value = ""; + + foreach (var s in args) + { + if (s.StartsWith("--") == true) + { + } + else if (s.StartsWith("-") == true) + { + if (!string.IsNullOrEmpty(key)) + { + var command = (from x in Commands + where x.FullName.Equals(key[1..], StringComparison.OrdinalIgnoreCase) || + x.ShortName.Equals(key[1..], StringComparison.OrdinalIgnoreCase) + select x).FirstOrDefault(); + + if (command != null) + { + argsDict.Add(command.FullName.ToLower(), value.Trim()); + } + + value = ""; + } + key = s; + } + else + { + value += " " + s; + } + } + + if (!string.IsNullOrEmpty(key)) + { + var command = (from x in Commands + where x.FullName.Equals(key[1..], StringComparison.OrdinalIgnoreCase) || + x.ShortName.Equals(key[1..], StringComparison.OrdinalIgnoreCase) + select x).FirstOrDefault(); + + if (command != null) + { + argsDict.Add(command.FullName.ToLower(), value.Trim()); + } + } + + return argsDict; + } + + /// + /// Returns the recognized arguments. + /// + /// The program arguments. + /// the recognized argument. + public string GetValidArguments(string[] args) + { + var argumentDict = Parse(args); + + var v = from x in argumentDict + select "-" + x.Key + (string.IsNullOrWhiteSpace(x.Value) ? "" : " " + x.Value); + + return string.Join(' ', v); + } + + /// + /// Returns the current ArgumentParser object. + /// + public static ArgumentParser Current + { + get + { + if (m_this == null) + { + m_this = new ArgumentParser(); + } + + return m_this; + } + } + } +} diff --git a/src/WebExpress/ArgumentParserCommand.cs b/src/WebExpress/ArgumentParserCommand.cs new file mode 100644 index 0000000..59d70f3 --- /dev/null +++ b/src/WebExpress/ArgumentParserCommand.cs @@ -0,0 +1,18 @@ +namespace WebExpress +{ + /// + /// A command which is controlled by the program arguments. + /// + public class ArgumentParserCommand + { + /// + /// The full name of the command. + /// + public string FullName { get; set; } + + /// + /// The short name of the command. + /// + public string ShortName { get; set; } + } +} diff --git a/src/WebExpress/Config/EndpointConfig.cs b/src/WebExpress/Config/EndpointConfig.cs new file mode 100644 index 0000000..435986a --- /dev/null +++ b/src/WebExpress/Config/EndpointConfig.cs @@ -0,0 +1,36 @@ +using System.Xml.Serialization; + +namespace WebExpress.Config +{ + /// + /// Class for reading certificate properties. + /// + [XmlRoot("endpoint", IsNullable = false)] + public sealed class EndpointConfig + { + /// + /// The uri (e.g. https://localhost:443/). + /// + [XmlAttribute("uri")] + public string Uri { get; set; } + + /// + /// The certificate as a pfx file. + /// + [XmlAttribute("pfx")] + public string PfxFile { get; set; } + + /// + /// The password. + /// + [XmlAttribute("password")] + public string Password { get; set; } + + /// + /// Constructor + /// + public EndpointConfig() + { + } + } +} \ No newline at end of file diff --git a/src/WebExpress/Config/HttpServerConfig.cs b/src/WebExpress/Config/HttpServerConfig.cs new file mode 100644 index 0000000..4e520d3 --- /dev/null +++ b/src/WebExpress/Config/HttpServerConfig.cs @@ -0,0 +1,80 @@ +using System.Collections.Generic; +using System.Xml.Serialization; +using WebExpress.Setting; + +namespace WebExpress.Config +{ + /// + /// Class for reading the configuration file. + /// + [XmlRoot("config", IsNullable = false)] + public sealed class HttpServerConfig + { + /// + /// The configuration version. + /// + [XmlAttribute("version", DataType = "int")] + public int Version { get; set; } + + /// + /// The endpoints of the web server. + /// + [XmlElement("endpoint")] + public List Endpoints { get; set; } + + /// + /// The uri of the web server. + /// + [XmlElement("uri")] + public string Uri { get; set; } + + /// + /// The limitations. + /// + [XmlElement("limit")] + public LimitConfig Limit { get; set; } + + /// + /// Root directory of packages. + /// + [XmlElement("packages")] + public string PackageBase { get; set; } + + /// + /// Root directory of assets. + /// + [XmlElement("assets")] + public string AssetBase { get; set; } + + /// + /// Root directory of the data. + /// + [XmlElement("data")] + public string DataBase { get; set; } + + /// + /// The uri base path of the web server. + /// + [XmlElement("contextpath")] + public string ContextPath { get; set; } + + /// + /// The culture + /// + [XmlElement("culture")] + public string Culture { get; set; } + + /// + /// The log settings. + /// + [XmlElement("log")] + public SettingLogItem Log { get; set; } + + /// + /// Constructor + /// + public HttpServerConfig() + { + } + } +} \ No newline at end of file diff --git a/src/WebExpress/Config/LimitConfig.cs b/src/WebExpress/Config/LimitConfig.cs new file mode 100644 index 0000000..cfccadc --- /dev/null +++ b/src/WebExpress/Config/LimitConfig.cs @@ -0,0 +1,30 @@ +using System.Xml.Serialization; + +namespace WebExpress.Config +{ + /// + /// Class for reading the limitations. + /// + [XmlRoot("limit", IsNullable = false)] + public sealed class LimitConfig + { + /// + /// The connection limit. + /// + [XmlElement("connectionlimit", DataType = "int")] + public int ConnectionLimit { get; set; } + + /// + /// The upload limit, in bytes. + /// + [XmlElement("uploadlimit", DataType = "long")] + public long UploadLimit { get; set; } + + /// + /// Constructor + /// + public LimitConfig() + { + } + } +} \ No newline at end of file diff --git a/src/WebExpress/Config/PluginConfig.cs b/src/WebExpress/Config/PluginConfig.cs new file mode 100644 index 0000000..bad6c70 --- /dev/null +++ b/src/WebExpress/Config/PluginConfig.cs @@ -0,0 +1,30 @@ +using System.Xml.Serialization; + +namespace WebExpress.Config +{ + /// + /// Class for reading the configuration file. + /// + [XmlRoot("config", IsNullable = false)] + public sealed class PluginConfig + { + /// + /// The configuration version. + /// + [XmlAttribute("version", DataType = "int")] + public int Version { get; set; } + + /// + /// The uri base path of the plugin. + /// + [XmlElement("contextpath")] + public string ContextPath { get; set; } + + /// + /// Constructor + /// + public PluginConfig() + { + } + } +} \ No newline at end of file diff --git a/src/WebExpress/HttpServer.cs b/src/WebExpress/HttpServer.cs new file mode 100644 index 0000000..e16a860 --- /dev/null +++ b/src/WebExpress/HttpServer.cs @@ -0,0 +1,581 @@ +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Hosting.Server; +using Microsoft.AspNetCore.Http.Features; +using Microsoft.AspNetCore.Server.Kestrel.Core; +using Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using System; +using System.Diagnostics; +using System.Globalization; +using System.Linq; +using System.Net; +using System.Net.Sockets; +using System.Security.Cryptography.X509Certificates; +using System.Threading; +using System.Threading.Tasks; +using WebExpress.Config; +using WebExpress.Internationalization; +using WebExpress.WebApplication; +using WebExpress.WebComponent; +using WebExpress.WebHtml; +using WebExpress.WebMessage; +using WebExpress.WebModule; +using WebExpress.WebPage; +using WebExpress.WebResource; +using WebExpress.WebSitemap; +using WebExpress.WebUri; + +namespace WebExpress +{ + /// + /// The web server for processing http requests (see RFC 2616). The web server uses Kestrel internally. + /// + public class HttpServer : IHost, II18N, IHttpApplication + { + /// + /// Event is triggered after the web server is started. + /// + public event EventHandler Started; + + /// + /// Provides the KestrelServer, which responds to the requests. + /// + private KestrelServer Kestrel { get; set; } + + /// + /// Server thread termination. + /// + private CancellationToken ServerToken { get; } = new CancellationToken(); + + /// + /// Returns or sets the configuration + /// + public HttpServerConfig Config { get; set; } + + /// + /// Returns or sets the context. + /// + public IHttpServerContext HttpServerContext { get; protected set; } + + /// + /// Returns the culture. + /// + public CultureInfo Culture { get; set; } + + /// + /// Returns the execution time of the web server. + /// + public static DateTime ExecutionTime { get; } = DateTime.Now; + + /// + /// Returns the request number; + /// + public long RequestNumber { get; private set; } + + /// + /// Constructor + /// + /// Der Serverkontext. + public HttpServer(HttpServerContext context) + { + HttpServerContext = new HttpServerContext + ( + context.Uri, + context.Endpoints, + context.PackagePath, + context.AssetPath, + context.DataPath, + context.ConfigPath, + context.ContextPath, + context.Culture, + context.Log, + this + ); + + Culture = HttpServerContext.Culture; + + ComponentManager.Initialization(HttpServerContext); + } + + /// + /// Starts the HTTP(S) server. + /// + public void Start() + { + if (HttpServerContext != null && HttpServerContext.Log != null) + { + HttpServerContext.Log.Info(message: this.I18N("webexpress:httpserver.run")); + } + + if (!HttpListener.IsSupported) + { + HttpServerContext.Log.Error(message: this.I18N("webexpress:httpserver.notsupported")); + } + + var logger = new LogFactory(); + var transportOptions = new OptionsWrapper(new SocketTransportOptions()); + var transport = new SocketTransportFactory(transportOptions, logger); + var serviceCollection = new ServiceCollection(); + + serviceCollection.AddMemoryCache(); + serviceCollection.AddLogging(x => + { + x.SetMinimumLevel(LogLevel.Trace); + x.AddProvider(logger); + }); + serviceCollection.AddHttpLogging(x => + { + x.LoggingFields = Microsoft.AspNetCore.HttpLogging.HttpLoggingFields.All; + }); + + var serverOptions = new OptionsWrapper(new KestrelServerOptions() + { + AllowSynchronousIO = true, + AllowResponseHeaderCompression = true, + AddServerHeader = true, + ApplicationServices = serviceCollection.BuildServiceProvider() + + }); + + serverOptions.Value.Limits.MaxConcurrentConnections = Config?.Limit?.ConnectionLimit > 0 ? Config?.Limit?.ConnectionLimit : serverOptions.Value.Limits.MaxConcurrentConnections; + serverOptions.Value.Limits.MaxRequestBodySize = Config?.Limit?.UploadLimit > 0 ? Config?.Limit?.UploadLimit : serverOptions.Value.Limits.MaxRequestBodySize; + + foreach (var endpoint in Config.Endpoints) + { + AddEndpoint(serverOptions, endpoint); + } + + Kestrel = new KestrelServer(serverOptions, transport, logger); + + Kestrel.StartAsync(this, ServerToken); + + HttpServerContext.Log.Info(message: this.I18N("webexpress:httpserver.start"), args: new object[] { ExecutionTime.ToShortDateString(), ExecutionTime.ToLongTimeString() }); + + Started?.Invoke(this, new EventArgs()); + } + + /// + /// Adds an endpoint. + /// + /// The server options. + /// The endpoint. + private void AddEndpoint(OptionsWrapper serverOptions, EndpointConfig endPoint) + { + try + { + var uri = new UriBuilder(endPoint.Uri); + var asterisk = uri.Host.Equals("*"); + + 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()) + .Where(x => x.AddressFamily == AddressFamily.InterNetwork || x.AddressFamily == AddressFamily.InterNetworkV6); + + HttpServerContext.Log.Info(message: this.I18N("webexpress:httpserver.endpoint"), args: endPoint.Uri); + + foreach (var ipAddress in addressList) + { + var ep = new IPEndPoint(ipAddress, port); + + switch (uri.Scheme) + { + case "HTTPS": { AddEndpoint(serverOptions, ep, endPoint.PfxFile, endPoint.Password); break; } + default: { AddEndpoint(serverOptions, ep); break; } + } + + } + } + catch (Exception ex) + { + HttpServerContext.Log.Error(message: this.I18N("webexpress:httpserver.listen.exeption"), args: endPoint); + HttpServerContext.Log.Exception(ex); + + } + } + + /// + /// Adds an endpoint. + /// + /// The server options. + /// The endpoint. + private void AddEndpoint(OptionsWrapper serverOptions, IPEndPoint endPoint) + { + serverOptions.Value.Listen(endPoint); + + HttpServerContext.Log.Info(message: this.I18N("webexpress:httpserver.listen"), args: endPoint.ToString()); + } + + /// + /// Adds an endpoint. + /// + /// The server options. + /// The certificate. + /// The password to the certificate. + /// The endpoint. + private void AddEndpoint(OptionsWrapper serverOptions, IPEndPoint endPoint, string pfxFile, string password) + { + serverOptions.Value.Listen(endPoint, configure => + { + var cert = new X509Certificate2(pfxFile, password); + + configure.UseHttps(cert); + }); + + HttpServerContext.Log.Info(message: this.I18N("webexpress:httpserver.listen"), args: endPoint.ToString()); + } + + /// + /// Stops the HTTP(S) server + /// + public void Stop() + { + // End running threads + Kestrel.StopAsync(ServerToken); + + // Stop running + ComponentManager.ShutDown(); + } + + /// + /// Handles an incoming request + /// Concurrent execution + /// + /// The context of the web request. + /// The response to be sent back to the caller. + private Response HandleClient(HttpContext context) + { + var stopwatch = Stopwatch.StartNew(); + var request = context.Request; + var response = default(Response); + 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 + ( + "webexpress:httpserver.request", + context.RemoteEndPoint, + ++RequestNumber, + $"{request?.Method} {request?.Uri} {request?.Protocoll}" + )); + + // search page in sitemap + var searchResult = ComponentManager.SitemapManager.SearchResource(context.Uri, new SearchContext() + { + Culture = culture, + HttpContext = context, + HttpServerContext = HttpServerContext + }); + + 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); + + request.Uri = resourceUri; + + try + { + // execute resource + request.AddParameter(searchResult.Uri.Parameters.Select(x => new Parameter(x.Key, x.Value, ParameterScope.Url))); + + if (searchResult.Instance != null) + { + searchResult.Instance?.PreProcess(request); + response = searchResult.Instance?.Process(request); + response = searchResult.Instance?.PostProcess(request, response); + + if (searchResult.Instance is IPage) + { + response.Content += $""; + } + + if (response is ResponseNotFound) + { + response = CreateStatusPage + ( + string.Empty, + request, + searchResult + ); + } + + if + ( + !response.Header.Cookies.Where(x => x.Name.Equals("session")).Any() && + !request.Header.Cookies.Where(x => x.Name.Equals("session")).Any() && + request.Session != null + ) + { + var cookie = new Cookie("session", request.Session.Id.ToString()) { Expires = DateTime.MaxValue }; + response.Header.Cookies.Add(cookie); + } + } + else + { + // Resource not found + response = CreateStatusPage + ( + string.Empty, + request, + searchResult + ); + } + } + catch (RedirectException ex) + { + if (ex.Permanet) + { + response = new ResponseRedirectPermanentlyMoved(ex.Url); + } + else + { + response = new ResponseRedirectTemporarilyMoved(ex.Url); + } + } + catch (Exception ex) + { + HttpServerContext.Log.Exception(ex); + + var message = $"

Message

{ex.Message}

" + + $"
Source
{ex.Source}

" + + $"
StackTrace
{ex.StackTrace.Replace("\n", "
\n")}

" + + $"
InnerException
{ex.InnerException?.ToString().Replace("\n", "
\n")}"; + + response = CreateStatusPage + ( + message, + request, + searchResult + ); + } + } + else + { + // Resource not found + response = CreateStatusPage(string.Empty, request); + } + + stopwatch.Stop(); + + HttpServerContext.Log.Info(InternationalizationManager.I18N + ( + "webexpress:httpserver.request.done", + context?.RemoteEndPoint, + RequestNumber, + stopwatch.ElapsedMilliseconds, + response.Status + )); + + return response; + } + + /// + /// Sends the response message + /// + /// The context of the request + /// The reply message + /// Sending the message as a task, which is executed concurrently. + private async Task SendResponseAsync(HttpContext context, Response response) + { + try + { + var responseFeature = context.Features.Get(); + var responseBodyFeature = context.Features.Get(); + + responseFeature.StatusCode = response.Status; + responseFeature.ReasonPhrase = response.Reason; + responseFeature.Headers.KeepAlive = "true"; + responseFeature.Headers.Add("PageID", "Test"); + + if (response.Header.Location != null) + { + responseFeature.Headers.Location = response.Header.Location; + } + + if (!string.IsNullOrWhiteSpace(response.Header.CacheControl)) + { + responseFeature.Headers.CacheControl = response.Header.CacheControl; + } + + if (!string.IsNullOrWhiteSpace(response.Header.ContentType)) + { + responseFeature.Headers.ContentType = response.Header.ContentType; + } + + if (response.Header.WWWAuthenticate) + { + responseFeature.Headers.WWWAuthenticate = "Basic realm=\"Bereich\""; + } + + if (response.Header.Cookies.Any()) + { + responseFeature.Headers.SetCookie = string.Join(" ", response.Header.Cookies); + } + + if (response?.Content is byte[] byteContent) + { + responseFeature.Headers.ContentLength = byteContent.Length; + await responseBodyFeature.Stream.WriteAsync(byteContent); + await responseBodyFeature.Stream.FlushAsync(); + } + else if (response?.Content is string strContent) + { + var content = context.Encoding.GetBytes(strContent); + + responseFeature.Headers.ContentLength = content.Length; + await responseBodyFeature.Stream.WriteAsync(content); + await responseBodyFeature.Stream.FlushAsync(); + } + else if (response?.Content is IHtmlNode htmlContent) + { + var content = context.Encoding.GetBytes(htmlContent?.ToString()); + + responseFeature.Headers.ContentLength = content.Length; + await responseBodyFeature.Stream.WriteAsync(content); + await responseBodyFeature.Stream.FlushAsync(); + } + + responseBodyFeature.Stream.Close(); + } + catch (Exception ex) + { + HttpServerContext.Log.Error(context.RemoteEndPoint + ": " + ex.Message); + } + } + + /// + /// Creates a status page + /// + /// 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() + { + 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 + ( + massage, + response.Status, + searchResult?.ModuleContext?.PluginContext ?? + searchResult?.ApplicationContext?.PluginContext + ); + + 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}

" + + $""; + + response.Content = message; + response.Header.ContentLength = message.Length; + response.Header.ContentType = "text/html; charset=utf-8"; + + return response; + } + + ///

+ /// Create an HttpContext with a collection of HTTP features. + /// + /// A collection of HTTP features to use to create the HttpContext. + /// The HttpContext created. + public HttpContext CreateContext(IFeatureCollection contextFeatures) + { + try + { + return new HttpContext(contextFeatures, this.HttpServerContext); + } + catch (Exception ex) + { + return new HttpExceptionContext(ex, contextFeatures); + } + } + + /// + /// Processes an http context asynchronously. + /// + /// The http context that the operation processes. + /// Provides an asynchronous operation that handles the http context. + public async Task ProcessRequestAsync(HttpContext context) + { + if (context is HttpExceptionContext exceptionContext) + { + var message = "404" + + $"

Message

{exceptionContext.Exception.Message}

" + + $"
Source
{exceptionContext.Exception.Source}

" + + $"
StackTrace
{exceptionContext.Exception.StackTrace.Replace("\n", "
\n")}

" + + $"
InnerException
{exceptionContext.Exception.InnerException?.ToString().Replace("\n", "
\n")}" + + ""; + + var response500 = CreateStatusPage(message, context?.Request); + + await SendResponseAsync(exceptionContext, response500); + + return; + } + + var response = HandleClient(context); + + await SendResponseAsync(context, response); + } + + /// + /// Discard a specified http context. + /// + /// The http context to discard. + /// The exception that is thrown if processing did not complete successfully; otherwise null. + public void DisposeContext(HttpContext context, Exception exception) + { + } + } +} diff --git a/src/WebExpress/HttpServerContext.cs b/src/WebExpress/HttpServerContext.cs new file mode 100644 index 0000000..eb4f588 --- /dev/null +++ b/src/WebExpress/HttpServerContext.cs @@ -0,0 +1,111 @@ +using System.Collections.Generic; +using System.Globalization; +using System.Reflection; +using WebExpress.Config; +using WebExpress.WebUri; + +namespace WebExpress +{ + /// + /// The context of the http server. + /// + public class HttpServerContext : IHttpServerContext + { + /// + /// Returns the uri of the web server. + /// + public string Uri { get; protected set; } + + /// + /// Returns the endpoints to which the web server responds. + /// + public ICollection Endpoints { get; protected set; } + + /// + /// Returns the version of the http(s) server. + /// + public string Version { get; protected set; } + + /// + /// Returns the package home directory. + /// + public string PackagePath { get; protected set; } + + /// + /// Returns the asset home directory. + /// + public string AssetPath { get; protected set; } + + /// + /// Returns the data home directory. + /// + public string DataPath { get; protected set; } + + /// + /// Returns the configuration directory. + /// + public string ConfigPath { get; protected set; } + + /// + /// Returns the basic context path. + /// + public UriResource ContextPath { get; protected set; } + + /// + /// Returns the culture. + /// + public CultureInfo Culture { get; protected set; } + + /// + /// Returns the log for writing status messages to the console and to a log file. + /// + public Log Log { get; protected set; } + + /// + /// Returns the host. + /// + public IHost Host { get; protected set; } + + /// + /// Constructor + /// + /// The uri of the web server. + /// The endpoints to which the web server responds. + /// The package home directory.chnis + /// The asset home directory. + /// The data home directory. + /// The configuration directory. + /// The basic context path. + /// The culture. + /// The log. + /// The host. + public HttpServerContext + ( + string uri, + ICollection endpoints, + string packageBaseFolder, + string assetBaseFolder, + string dataBaseFolder, + string configBaseFolder, + UriResource contextPath, + CultureInfo culture, + Log log, + IHost host + ) + { + var assembly = typeof(HttpServer).Assembly; + Version = assembly.GetCustomAttribute()?.InformationalVersion; + + Uri = uri; + Endpoints = endpoints; + PackagePath = packageBaseFolder; + AssetPath = assetBaseFolder; + DataPath = dataBaseFolder; + ConfigPath = configBaseFolder; + ContextPath = contextPath; + Culture = culture; + Log = log; + Host = host; + } + } +} diff --git a/src/WebExpress/IHost.cs b/src/WebExpress/IHost.cs new file mode 100644 index 0000000..72e4eed --- /dev/null +++ b/src/WebExpress/IHost.cs @@ -0,0 +1,20 @@ +using System; + +namespace WebExpress +{ + /// + /// The host interface. + /// + public interface IHost + { + /// + /// Returns the context of the host. + /// + IHttpServerContext HttpServerContext { get; } + + /// + /// Event is triggered after the web server starts. + /// + event EventHandler Started; + } +} diff --git a/src/WebExpress/IHttpServerContext.cs b/src/WebExpress/IHttpServerContext.cs new file mode 100644 index 0000000..7728ad1 --- /dev/null +++ b/src/WebExpress/IHttpServerContext.cs @@ -0,0 +1,68 @@ +using System.Collections.Generic; +using System.Globalization; +using WebExpress.Config; +using WebExpress.WebUri; + +namespace WebExpress +{ + /// + /// The context interface of the http server. + /// + public interface IHttpServerContext + { + /// + /// Returns the uri of the web server. + /// + string Uri { get; } + + /// + /// Returns the endpoints to which the web server responds. + /// + ICollection Endpoints { get; } + + /// + /// Returns the version of the http(s) server. + /// + string Version { get; } + + /// + /// Returns the package home directory. + /// + string PackagePath { get; } + + /// + /// Returns the asset home directory. + /// + string AssetPath { get; } + + /// + /// Returns the data home directory. + /// + string DataPath { get; } + + /// + /// Returns the configuration directory. + /// + string ConfigPath { get; } + + /// + /// Returns the basic context path. + /// + UriResource ContextPath { get; } + + /// + /// Returns the culture. + /// + CultureInfo Culture { get; } + + /// + /// Returns the log for writing status messages to the console and to a log file. + /// + Log Log { get; } + + /// + /// Returns the host. + /// + IHost Host { get; } + } +} diff --git a/src/WebExpress/Internationalization/II18N.cs b/src/WebExpress/Internationalization/II18N.cs new file mode 100644 index 0000000..978d6bb --- /dev/null +++ b/src/WebExpress/Internationalization/II18N.cs @@ -0,0 +1,12 @@ +using System.Globalization; + +namespace WebExpress.Internationalization +{ + public interface II18N + { + /// + /// Returns or sets the culture. + /// + CultureInfo Culture { get; set; } + } +} diff --git a/src/WebExpress/Internationalization/InternationalizationDictionary.cs b/src/WebExpress/Internationalization/InternationalizationDictionary.cs new file mode 100644 index 0000000..0a8f5d8 --- /dev/null +++ b/src/WebExpress/Internationalization/InternationalizationDictionary.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; + +namespace WebExpress.Internationalization +{ + /// + /// key = language (ISO 639-1 two-letter) + /// value = { key = , value = } + /// + internal class InternationalizationDictionary : Dictionary + { + } +} diff --git a/src/WebExpress/Internationalization/InternationalizationExtensions.cs b/src/WebExpress/Internationalization/InternationalizationExtensions.cs new file mode 100644 index 0000000..5c0473d --- /dev/null +++ b/src/WebExpress/Internationalization/InternationalizationExtensions.cs @@ -0,0 +1,42 @@ +using WebExpress.WebApplication; + +namespace WebExpress.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/Internationalization/InternationalizationItem.cs b/src/WebExpress/Internationalization/InternationalizationItem.cs new file mode 100644 index 0000000..75687c1 --- /dev/null +++ b/src/WebExpress/Internationalization/InternationalizationItem.cs @@ -0,0 +1,8 @@ +using System.Collections.Generic; + +namespace WebExpress.Internationalization +{ + internal class InternationalizationItem : Dictionary + { + } +} diff --git a/src/WebExpress/Internationalization/InternationalizationManager.cs b/src/WebExpress/Internationalization/InternationalizationManager.cs new file mode 100644 index 0000000..49250a1 --- /dev/null +++ b/src/WebExpress/Internationalization/InternationalizationManager.cs @@ -0,0 +1,237 @@ +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Reflection; +using WebExpress.WebMessage; +using WebExpress.WebComponent; +using WebExpress.WebPlugin; + +namespace WebExpress.Internationalization +{ + /// + /// Internationalization + /// + public sealed class InternationalizationManager : IComponentPlugin, ISystemComponent + { + /// + /// Returns the default language. + /// + public static CultureInfo DefaultCulture { get; private set; } = CultureInfo.CurrentCulture; + + /// + /// Returns the directory by listing the internationalization key-value pairs. + /// + private static InternationalizationDictionary Dictionary { get; } = new InternationalizationDictionary(); + + /// + /// Returns or sets the reference to the context of the host. + /// + public IHttpServerContext HttpServerContext { get; private set; } + + /// + /// Constructor + /// + internal InternationalizationManager() + { + 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; + DefaultCulture = HttpServerContext.Culture; + + HttpServerContext.Log.Debug + ( + I18N("webexpress:internationalizationmanager.initialization") + ); + } + + /// + /// Discovers and registers entries from the specified plugin. + /// + /// A context of a plugin whose elements are to be registered. + public void Register(IPluginContext pluginContext) + { + var pluginId = pluginContext.PluginId; + Register(pluginContext.Assembly, pluginId); + + HttpServerContext.Log.Debug + ( + I18N("webexpress:internationalizationmanager.register", pluginId) + ); + } + + /// + /// Discovers and registers entries from the specified plugin. + /// + /// A list with plugin contexts that contain the elements. + public void Register(IEnumerable pluginContexts) + { + foreach (var pluginContext in pluginContexts) + { + Register(pluginContext); + } + } + + /// + /// Adds the internationalization key-value pairs from the specified assembly. + /// + /// 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) + { + var assemblyName = assembly.GetName().Name.ToLower(); + var name = assemblyName + ".internationalization."; + var resources = assembly.GetManifestResourceNames().Where(x => x.ToLower().Contains(name)); + + foreach (var languageResource in resources) + { + var language = languageResource.Split('.').LastOrDefault()?.ToLower(); + + if (!Dictionary.ContainsKey(language)) + { + Dictionary.Add(language, new InternationalizationItem()); + } + + var dictItem = Dictionary[language]; + + using var stream = assembly.GetManifestResourceStream(languageResource); + using var streamReader = new StreamReader(stream); + while (!streamReader.EndOfStream) + { + var line = streamReader.ReadLine(); + if (!line.StartsWith('#') && !string.IsNullOrWhiteSpace(line)) + { + var split = line.Split('='); + var key = pluginId?.ToLower() + ":" + split[0]?.Trim().ToLower(); + + if (!dictItem.ContainsKey(key)) + { + dictItem.Add(key, string.Join("=", split.Skip(1))); + } + } + } + } + } + + /// + /// Removes all internationalization key-value pairs associated with the specified plugin context. + /// + /// The context of the plugin containing the key-value pairs to remove. + public void Remove(IPluginContext pluginContext) + { + + } + + /// + /// 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(II18N obj, string key) + { + return I18N(obj.Culture, key); + } + + /// + /// Internationalization of a key. + /// + /// 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) + { + return I18N(request.Culture, null, key); + } + + /// + /// Internationalization of a key. + /// + /// The culture with the language to use. + /// The internationalization key. + /// The value of the key in the current language. + public static string I18N(CultureInfo culture, string key) + { + return I18N(culture, null, key); + } + + /// + /// Internationalization of a key. + /// + /// 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) + { + var language = culture?.TwoLetterISOLanguageName; + var k = string.IsNullOrWhiteSpace(key) || string.IsNullOrWhiteSpace(pluginId) || key.StartsWith($"{pluginId?.ToLower()}:") ? key?.ToLower() : $"{pluginId?.ToLower()}:{key?.ToLower()}"; + + if (string.IsNullOrWhiteSpace(key)) + { + return null; + } + + if (string.IsNullOrWhiteSpace(language) || language == "*") + { + language = DefaultCulture?.TwoLetterISOLanguageName; + } + + var item = Dictionary[language]; + + if (item.ContainsKey(k)) + { + return item[k]; + } + + return key; + } + + /// + /// Internationalization of a key. + /// + /// The internationalization key. + /// The value of the key in the current language. + public static string I18N(string key) + { + return I18N(DefaultCulture, null, key); + } + + /// + /// Internationalization of a key. + /// + /// The internationalization key. + /// The formatting arguments. + /// The value of the key in the current language. + public static string I18N(string key, params object[] args) + { + return string.Format(I18N(DefaultCulture, null, key), args); + } + + /// + /// 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) + { + + } + } +} diff --git a/src/WebExpress/Internationalization/de b/src/WebExpress/Internationalization/de new file mode 100644 index 0000000..8880cbc --- /dev/null +++ b/src/WebExpress/Internationalization/de @@ -0,0 +1,131 @@ +# Deutsch + +app.startup=Programmstart +app.version=Programmversion: {0} +app.arguments=Argumente: {0} +app.workingdirectory=Arbeitsverzeichnis: {0} +app.packagebase=Paketverzeichnis: {0} +app.assetbase=Assetverzeichnis: {0} +app.database=Datenverzeichnis: {0} +app.configurationdirectory=Konfigurationsverzeichnis: {0} +app.configuration=Konfiguration: {0} +app.logdirectory=Logverzeichnis: {0} +app.log=Log: {0} +app.uri=Uri: {0} +app.errors={0} Fehler +app.warnings={0} Warnung(en) +app.done=Programmende + +httpserver.run=Starte HttpServer. +httpserver.notsupported=Das Betriebssystem auf dem WebExpress ausgeführt werden soll wird nicht unterstützt. +httpserver.start=Der HttpServer wurde am {0} um {1} Uhr gestartet. +httpserver.connected={0}: Client wurde verbunden. +httpserver.rejected={0}: Client wurde abgelehnt. +httpserver.request={0}: Anfrage #{1} '{2}' +httpserver.request.unexpected={0}: Unerwartete Anfrage '{1}' +httpserver.request.done={0}: Anfrage #{1} wurde in {2} ms bearbeitet. Status = {3} +httpserver.connectionlimit=Das Limit für gleichzeitige Anfragen wurde überschritten. +httpserver.endpoint=Endpunkt '{0}' wird registriert. +httpserver.listen=Der Webserver lauscht auf den Endpunkt {0}. +httpserver.listen.exeption=Fehler beim Einrichten eines Endpunktes für die Uri {0}. +httpserver.listener.try=Falls Ihnen Zugriffsrechte fehlen, führen Sie unter Windows folgende Anweisung als Administrator aus: +httpserver.listener.windows=netsh http add urlacl url={0} user=Jeder listen=yes + +componentmanager.initialization=Der Komponentenmanager wurde initialisiert. +componentmanager.register=Die Komponente '{0}' wurde im Komponentenmanager registriert. +componentmanager.execute=Startet den Komponenten-Manager. +componentmanager.shutdown=Herunterfahren des Komponentenmanagers. +componentmanager.wrongtype=Der Typ '{0}' implementiert die Schnittstelle '{1}' nicht. +componentmanager.duplicate=Die Komponente '{0}' wurde bereits registriert. +componentmanager.remove=Die Komponente '{0}' wurde entfernt. +componentmanager.component=Komponenten: + +internationalizationmanager.initialization=Der Internationalisierungsmanager wurde initialisiert. +internationalizationmanager.register=Das Plugin '{0}' wurde im Internationalizationmanager registriert. + +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. +packagemanager.remove=Das bestehende Paket '{0}' wird aus dem Katalog entfernt. +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. + +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.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. + +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. +applicationmanager.application=Anwendung: '{0}' +applicationmanager.application.initialization=Die Anwendung '{0}' wurde initialisiert. +applicationmanager.application.processing.start=Die Anwendung '{0}' wird ausgeführt. +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.wrongtype=Der Type '{0}' implementiert die Schnittstelle '{1}' nicht. +resourcemanager.resource=Ressource: '{0}' für das Modul '{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}' + +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. + +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}' +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. + +resource.variable.duplicate=Variable '{0}' bereits vorhanden! +resource.file={0}: Datei '{1}' wurde geladen. + diff --git a/src/WebExpress/Internationalization/en b/src/WebExpress/Internationalization/en new file mode 100644 index 0000000..164d882 --- /dev/null +++ b/src/WebExpress/Internationalization/en @@ -0,0 +1,130 @@ +# Englisch + +app.startup=Startup +app.version=Version: {0} +app.arguments=Arguments: {0} +app.workingdirectory=Working directory: {0} +app.packagebase=Package directory: {0} +app.assetbase=Asset directory: {0} +app.database=Data directory: {0} +app.configurationdirectory=Configuration directory: {0} +app.configuration=Configuration: {0} +app.logdirectory=Log directory: {0} +app.log=Log: {0} +app.uri=Uri: {0} +app.errors={0} Errors +app.warnings={0} Warnings +app.done=End of program + +httpserver.run=Run HttpServer. +httpserver.notsupported=The operating system on which WebExpress is to be run is not supported. +httpserver.start=The HttpServer was started on {0} at {1} o'clock. +httpserver.connected={0}: Client has been connected. +httpserver.rejected={0}: Client rejected. +httpserver.request={0}: Request '{1}' +httpserver.unexpected.request={0}: Unexpected request '{1}' +httpserver.request.done={0}: Request has been processed in {1} ms. Status = {2} +httpserver.connectionlimit=The limit for concurrent requests has been exceeded. +httpserver.endpoint=Endpoint '{0}' is registered. +httpserver.listen=The web server listens for the endpoint {0}. +httpserver.listen.exeption=Failed to set up an endpoint for the Uri {0}. +httpserver.listener.try=If you do not have access rights follow the instruction as an administrator in the console: +httpserver.listener.windows=netsh http add urlacl url={0} user=everyone listen=yes + +componentmanager.initialization=The component manager has been initialized. +componentmanager.register=The component '{0}' has been registered in the component manager. +componentmanager.execute=Starts the component manager. +componentmanager.shutdown=Shutting down the component manager. +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: + +internationalizationmanager.initialization=The internationalization manager has been initialized. +internationalizationmanager.register=The plugin '{0}' is registered in the internationalization 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. +packagemanager.remove=The existing package '{0}' is removed from the catalog. +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. + +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.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}'. + +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. +applicationmanager.application=Application: '{0}' +applicationmanager.application.initialization=The application'{0}' has been initialized. +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.sitemap=Sitemap content resource '{0}' application: +resourcemanager.wrongtype=The type '{0}' does not implement the interface '{1}'. +resourcemanager.statuspage=Resource: '{0}' for module '{1}' + +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}' + +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. + +eventmanager.initialization=The event manager has been initialized. + +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.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. + +resource.variable.duplicate=Variable '{0}' already exists! +resource.file={0}: File '{1}' has been loaded. \ No newline at end of file diff --git a/src/WebExpress/Log.cs b/src/WebExpress/Log.cs new file mode 100644 index 0000000..87cb420 --- /dev/null +++ b/src/WebExpress/Log.cs @@ -0,0 +1,582 @@ +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading; +using WebExpress.Setting; + +namespace WebExpress +{ + /// + /// Class 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 class Log : ILogger + { + /// + /// Enumeration defines the different log levels. + /// + public enum Level { Info, Warning, FatalError, Error, Exception, Debug, Seperartor }; + + /// + /// Enumerations of the log mode. + /// + public enum Mode { Off, Append, Override }; + + /// + /// Returns or sets the encoding. + /// + public Encoding Encoding { get; set; } + + /// + /// Determines whether to display debug output. + /// + public bool DebugMode { get; private set; } = false; + + /// + /// Returns the file name of the log + /// + public string Filename { get; set; } + + /// + /// Returns the number of exceptions. + /// + public int ExceptionCount { get; protected set; } + + /// + /// Returns the number of errors (errors + exceptions). + /// + public int ErrorCount { get; protected set; } + + /// + /// Returns the number of warnings. + /// + public int WarningCount { get; protected set; } + + /// + /// Checks if the log has been opened for writing. + /// + public bool IsOpen => _workerThread != null; + + /// + /// Returns the log mode. + /// + public Mode LogMode { get; set; } + + /// + /// The default instance of the logger. + /// + 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. + /// + private const int _seperatorWidth = 260; + + /// + /// End worker thread lifecycle. + /// + private bool _done = false; + + /// + /// Unsaved entries queue. + /// + private readonly Queue _queue = new Queue(); + + /// + /// Returns the number of characters for log outputs in the console. + /// + private int Width + { + get + { + try + { + return Console.WindowWidth; + } + catch + { + } + + return 250; + } + } + + /// + /// Constructor + /// + public Log() + { + Encoding = Encoding.UTF8; + FilePattern = "yyyyMMdd"; + TimePattern = "yyyMMddHHmmss"; + LogMode = Log.Mode.Append; + } + + /// + /// Starts logging + /// + /// The path where the log file is created. + /// The file name of the log file. + public void Begin(string path, string name) + { + Filename = Path.Combine(path, name); + _path = path; + + // check directory + if (!Directory.Exists(_path)) + { + // no log directory exists yet -create > + Directory.CreateDirectory(_path); + } + + // Delete existing log file when overwrite mode is active + if (LogMode == Mode.Override) + { + try + { + File.Delete(Filename); + } + catch + { + } + } + + // create thread + _workerThread = new Thread(new ThreadStart(ThreadProc)) + { + + // Background thread + IsBackground = true + }; + + _workerThread.Start(); + } + + /// + /// Starts logging + /// + /// The path where the log file is created. + public void Begin(string path) + { + Begin(path, DateTime.Today.ToString(FilePattern) + ".log"); + } + + /// + /// Starts logging + /// + /// The log settings + public void Begin(SettingLogItem settings) + { + Filename = settings.Filename; + LogMode = (Mode)Enum.Parse(typeof(Mode), settings.Modus); + Encoding = Encoding.GetEncoding(settings.Encoding); + TimePattern = settings.Timepattern; + DebugMode = settings.Debug; + + Begin(settings.Path, Filename); + } + + /// + /// Adds a message to the log. + /// + /// The Level. + /// The log message. + /// 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) + { + foreach (var l in message?.Split(Environment.NewLine.ToCharArray(), StringSplitOptions.RemoveEmptyEntries)) + { + lock (_queue) + { + var item = new LogItem(level, instance, l, TimePattern); + switch (level) + { + case Level.Error: + case Level.FatalError: + case Level.Exception: + Console.ForegroundColor = ConsoleColor.Red; + break; + case Level.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.ResetColor(); + + _queue.Enqueue(item); + } + } + } + + /// + /// A dividing line with * characters + /// + public void Seperator() + { + Seperator('*'); + } + + /// + /// A separator with custom characters + /// + /// The separator. + public void Seperator(char sepChar) + { + Add(Level.Seperartor, "".PadRight(_seperatorWidth, 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) + { + var methodInfo = new StackTrace().GetFrame(1).GetMethod(); + var className = methodInfo.ReflectedType.Name; + + Add(Level.Info, message, $"{className}.{instance}", line, file); + } + + /// + /// 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) + { + var methodInfo = new StackTrace().GetFrame(1).GetMethod(); + var className = methodInfo.ReflectedType.Name; + + Add(Level.Info, string.Format(message, args), $"{className}.{instance}", line, file); + } + + /// + /// 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) + { + var methodInfo = new StackTrace().GetFrame(1).GetMethod(); + var className = methodInfo.ReflectedType.Name; + + Add(Level.Warning, message, $"{className}.{instance}", line, file); + + WarningCount++; + } + + /// + /// 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) + { + var methodInfo = new StackTrace().GetFrame(1).GetMethod(); + var className = methodInfo.ReflectedType.Name; + + Add(Level.Warning, string.Format(message, args), $"{className}.{instance}", line, file); + + WarningCount++; + } + + /// + /// 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) + { + var methodInfo = new StackTrace().GetFrame(1).GetMethod(); + var className = methodInfo.ReflectedType.Name; + + Add(Level.Error, message, $"{className}.{instance}", line, file); + + ErrorCount++; + } + + /// + /// 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) + { + var methodInfo = new StackTrace().GetFrame(1).GetMethod(); + var className = methodInfo.ReflectedType.Name; + + Add(Level.Error, string.Format(message, args), $"{className}.{instance}", line, file); + + ErrorCount++; + } + + /// + /// 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) + { + var methodInfo = new StackTrace().GetFrame(1).GetMethod(); + var className = methodInfo.ReflectedType.Name; + + Add(Level.FatalError, message, $"{className}.{instance}", line, file); + + ErrorCount++; + } + + /// + /// 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) + { + var methodInfo = new StackTrace().GetFrame(1).GetMethod(); + var className = methodInfo.ReflectedType.Name; + + Add(Level.FatalError, string.Format(message, args), $"{className}.{instance}", line, file); + + ErrorCount++; + } + + /// + /// 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) + { + var methodInfo = new StackTrace().GetFrame(1).GetMethod(); + var className = methodInfo.ReflectedType.Name; + + 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); + + ExceptionCount++; + ErrorCount++; + } + } + + /// + /// 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) + { + var methodInfo = new StackTrace().GetFrame(1).GetMethod(); + var className = methodInfo.ReflectedType.Name; + + if (DebugMode) + { + Add(Level.Debug, message, $"{className}.{instance}", line, file); + } + } + + /// + /// 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) + { + var methodInfo = new StackTrace().GetFrame(1).GetMethod(); + var className = methodInfo.ReflectedType.Name; + + if (DebugMode) + { + Add(Level.Debug, string.Format(message, args), $"{className}.{instance}", line, file); + } + } + + /// + /// Stops logging. + /// + public void Close() + { + _done = true; + + // protect file writing from concurrent access + lock (_path) + { + Flush(); + } + } + + /// + /// Cleans up the log. + /// + public void Clear() + { + ErrorCount = 0; + WarningCount = 0; + ExceptionCount = 0; + } + + /// + /// Writes the contents of the queue to the log. + /// + public void Flush() + { + var list = new List(); + + // lock queue before concurrent access + lock (_queue) + { + list.AddRange(_queue); + _queue.Clear(); + } + + // protect file writing from concurrent access + if (list.Count > 0 && LogMode != Mode.Off) + { + lock (_path) + { + using var fs = new FileStream(Filename, FileMode.Append); + using var w = new StreamWriter(fs, Encoding); + foreach (var item in list) + { + var str = item.ToString(); + w.WriteLine(str); + } + } + } + } + + /// + /// Thread Start Function + /// + private void ThreadProc() + { + while (!_done) + { + Thread.Sleep(5000); + + // protect file writing from concurrent access + lock (_path) + { + Flush(); + } + } + + _workerThread = null; + } + + /// + /// Writes a log entry. + /// + /// The type of object to write. + /// The entry is written at this level. + /// Id of the event. + /// 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) + { + if (logLevel == LogLevel.Error) + { + var message = exception?.Message ?? formatter.Invoke(state, exception); + Error(message, "Kestrel", null, null); + } + + } + + /// + /// Verifies that the specified logLevel parameter is enabled. + /// + /// Level to be checked. + /// True in the enabled state, false otherwise. + public bool IsEnabled(LogLevel logLevel) + { + return true; + } + + /// + /// Formats the message and creates a range. + /// + /// The type of object to write. + /// The ILogger interface in which to create the scope. + /// A disposable range object. Can be NULL. + 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/LogFactory.cs b/src/WebExpress/LogFactory.cs new file mode 100644 index 0000000..84711e2 --- /dev/null +++ b/src/WebExpress/LogFactory.cs @@ -0,0 +1,36 @@ +using Microsoft.Extensions.Logging; + +namespace WebExpress +{ + public class LogFactory : ILoggerFactory, ILoggerProvider + { + /// + /// Adds an ILoggerProvider to the logging system. + /// + /// The ILoggerProvider. + public void AddProvider(ILoggerProvider provider) + { + + } + + /// + /// Creates a new ILogger instance. + /// + /// The category name for messages generated by logging. + /// A new ILogger instance. + public ILogger CreateLogger(string categoryName) + { + // use an existing logging instance + return Log.Current; + } + + /// + /// Performs application-specific tasks related to sharing, returning, or + /// resetting unmanaged resources. + /// + public void Dispose() + { + + } + } +} diff --git a/src/WebExpress/LogFrame.cs b/src/WebExpress/LogFrame.cs new file mode 100644 index 0000000..c50fbd6 --- /dev/null +++ b/src/WebExpress/LogFrame.cs @@ -0,0 +1,80 @@ +using System; +using System.Runtime.CompilerServices; + +namespace WebExpress +{ + /// + /// Creates a frame of log entries. + /// + public class LogFrame : IDisposable + { + /// + /// The status. + /// + public string Status { get; set; } + + /// + /// Method that wants to log. + /// + protected string Instance { get; set; } + + /// + /// The line number. + /// + protected int Line { get; set; } + + /// + /// The source file. + /// + protected string File { get; set; } + + /// + /// The log entry. + /// + protected Log Log { get; set; } + + /// + /// Constructor + /// + /// The log entry. + /// The name. + /// An additional heading or zero. + /// Method that wants to log. + /// The line number. + /// The source file. + public LogFrame(Log log, string name, string additionalHeading = null, [CallerMemberName] string instance = null, [CallerLineNumber] int? line = null, [CallerFilePath] string file = null) + { + Instance = instance; + Status = string.Format("{0} abgeschlossen. ", name); + + Log = log; + Log.Seperator(); + Log.Info(string.Format("Beginne mit {0}", name) + (!string.IsNullOrWhiteSpace(additionalHeading) ? " " + additionalHeading : ""), instance, line, file); + Log.Info("".PadRight(80, '-'), instance, line, file); + } + + /// + /// Constructor + /// + /// The name. + /// The additional heading or zero. + /// Method that wants to log. + /// The line number. + /// The source file. + public LogFrame(string name, string additionalHeading = null, [CallerMemberName] string instance = null, [CallerLineNumber] int? line = null, [CallerFilePath] string file = null) + : this(Log.Current, name, additionalHeading, instance, line, file) + { + } + + /// + /// 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); + } + } +} diff --git a/src/WebExpress/LogFrameSimple.cs b/src/WebExpress/LogFrameSimple.cs new file mode 100644 index 0000000..7cb8a19 --- /dev/null +++ b/src/WebExpress/LogFrameSimple.cs @@ -0,0 +1,58 @@ +using System; +using System.Runtime.CompilerServices; + +namespace WebExpress +{ + /// + /// Creates a frame of log entries. + /// + public class LogFrameSimple : IDisposable + { + /// + /// Method that wants to log. + /// + protected string Instance { get; set; } + + /// + /// The line number. + /// + protected int Line { get; set; } + + /// + /// The source file. + /// + protected string File { get; set; } + + /// + /// The log entry. + /// + protected Log Log { get; set; } + + /// + /// Constructor + /// + /// 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) + { + Instance = instance; + Log = log; + Line = line ?? 0; + File = file; + + Log.Info("".PadRight(80, '>'), instance, line, file); + } + + /// + /// 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); + } + } +} diff --git a/src/WebExpress/LogItem.cs b/src/WebExpress/LogItem.cs new file mode 100644 index 0000000..44e681a --- /dev/null +++ b/src/WebExpress/LogItem.cs @@ -0,0 +1,88 @@ +using System; +using static WebExpress.Log; + +namespace WebExpress +{ + /// + /// Log entry + /// + internal class LogItem + { + /// + /// Level of the entry. + /// + private readonly Level m_level; + + /// + /// The instance (location). + /// + private readonly string m_instance; + + /// + /// The log message. + /// + private readonly string m_message; + + /// + /// The timestamp. + /// + private readonly DateTime m_timestamp; + + /// + /// Constructor + /// + /// The level. + /// The modul/funktion. + /// The log message. + public LogItem(Level level, string instance, string message, string timePattern) + { + m_level = level; + m_instance = instance; + m_message = message; + m_timestamp = DateTime.Now; + TimePattern = timePattern; + } + + /// + /// Converts the value of this instance to a string. + /// + /// The log entry as a string + public override string ToString() + { + if (m_level != Level.Seperartor) + { + return m_timestamp.ToString(TimePattern) + " " + m_level.ToString().PadRight(9, ' ') + " " + m_instance.PadRight(19, ' ').Substring(0, 19) + " " + m_message; + } + else + { + return m_message; + } + } + + /// + /// Returns the level of the entry. + /// + public Level Level => m_level; + + /// + /// Returns the instance (location). + /// + public string Instance => m_instance; + + /// + /// Returns the message. + /// + public string Message => m_message; + + /// + /// Returns the timestamp. + /// + public DateTime Timestamp => m_timestamp; + + + /// + /// Returns the or set the time patterns for log entries. + /// + public string TimePattern { set; get; } + }; +} diff --git a/src/WebExpress/Properties/launchSettings.json b/src/WebExpress/Properties/launchSettings.json new file mode 100644 index 0000000..9106c1c --- /dev/null +++ b/src/WebExpress/Properties/launchSettings.json @@ -0,0 +1,12 @@ +{ + "profiles": { + "WebExpress": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "https://localhost:51508;http://localhost:51509" + } + } +} \ No newline at end of file diff --git a/src/WebExpress/Rocket.ico b/src/WebExpress/Rocket.ico new file mode 100644 index 0000000000000000000000000000000000000000..c0909c98b13c6125b7254544eaa2a1d91238b006 GIT binary patch literal 70141 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 BFy8 + /// Class for managing log settings. + /// + [XmlType("log")] + public class SettingLogItem : ISettingItem + { + /// + /// The log mode. + /// + [XmlAttribute(AttributeName = "modus")] + public string Modus { get; set; } + + /// + /// Determines whether to display debug output. + /// + [XmlAttribute(AttributeName = "debug")] + public bool Debug { get; set; } = false; + + /// + /// The directory where the log is created. + /// + [XmlAttribute(AttributeName = "path")] + public string Path { get; set; } + + /// + /// The encoding settings. + /// + [XmlAttribute(AttributeName = "encoding")] + public string Encoding { get; set; } + + /// + /// The file name of the log. + /// + [XmlAttribute(AttributeName = "filename")] + public string Filename { get; set; } + + /// + /// The format of the timestamp. + /// + [XmlAttribute(AttributeName = "timepattern")] + public string Timepattern { get; set; } + + /// + /// Constructor + /// + public SettingLogItem() + { + } + } +} diff --git a/src/WebExpress/WebApplication/ApplicationContext.cs b/src/WebExpress/WebApplication/ApplicationContext.cs new file mode 100644 index 0000000..c48fac4 --- /dev/null +++ b/src/WebExpress/WebApplication/ApplicationContext.cs @@ -0,0 +1,70 @@ +using System.Collections.Generic; +using WebExpress.WebPlugin; +using WebExpress.WebUri; + +namespace WebExpress.WebApplication +{ + public class ApplicationContext : IApplicationContext + { + /// + /// Returns the context of the associated plugin. + /// + public IPluginContext PluginContext { get; internal set; } + + /// + /// Returns the application id. + /// + public string ApplicationId { get; internal set; } + + /// + /// Returns the application name. + /// + public string ApplicationName { get; internal set; } + + /// + /// Returns or sets the description. + /// + 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. + /// + public string AssetPath { get; internal set; } + + /// + /// Returns the data directory. This is mounted in the data directory of the server. + /// + public string DataPath { get; internal set; } + + /// + /// Returns the context path. This is mounted in the context path of the server. + /// + public UriResource ContextPath { get; internal set; } + + /// + /// Returns the icon uri. + /// + public UriResource Icon { get; internal set; } + + /// + /// Constructor + /// + public ApplicationContext() + { + } + + /// + /// Conversion of the apllication context into its string representation. + /// + /// The string that uniquely represents the application. + public override string ToString() + { + return $"Application {ApplicationId}"; + } + } +} diff --git a/src/WebExpress/WebApplication/ApplicationDictionary.cs b/src/WebExpress/WebApplication/ApplicationDictionary.cs new file mode 100644 index 0000000..4f72375 --- /dev/null +++ b/src/WebExpress/WebApplication/ApplicationDictionary.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; +using WebExpress.WebPlugin; + +namespace WebExpress.WebApplication +{ + /// + /// Key = Plugin context + /// Value = { Key = application id, Value = application item } + /// + internal class ApplicationDictionary : Dictionary> + { + } +} diff --git a/src/WebExpress/WebApplication/ApplicationItem.cs b/src/WebExpress/WebApplication/ApplicationItem.cs new file mode 100644 index 0000000..e3a77c6 --- /dev/null +++ b/src/WebExpress/WebApplication/ApplicationItem.cs @@ -0,0 +1,25 @@ +using System.Threading; + +namespace WebExpress.WebApplication +{ + /// + /// Represents an application entry in the application directory. + /// + internal class ApplicationItem + { + /// + /// The context associated with the application. + /// + public IApplicationContext ApplicationContext { get; set; } + + /// + /// The application. + /// + public IApplication Application { get; set; } + + /// + /// Thread termination token. + /// + public CancellationTokenSource CancellationTokenSource { get; } = new CancellationTokenSource(); + } +} diff --git a/src/WebExpress/WebApplication/ApplicationManager.cs b/src/WebExpress/WebApplication/ApplicationManager.cs new file mode 100644 index 0000000..d0f7fa6 --- /dev/null +++ b/src/WebExpress/WebApplication/ApplicationManager.cs @@ -0,0 +1,414 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using WebExpress.Internationalization; +using WebExpress.WebAttribute; +using WebExpress.WebComponent; +using WebExpress.WebPlugin; +using WebExpress.WebUri; + +namespace WebExpress.WebApplication +{ + /// + /// Management of WebExpress applications. + /// + public sealed class ApplicationManager : IComponentPlugin, IExecutableElements, ISystemComponent + { + /// + /// An event that fires when an application is added. + /// + public event EventHandler AddApplication; + + /// + /// An event that fires when an application is removed. + /// + 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); + + /// + /// Constructor + /// + internal ApplicationManager() + { + 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:applicationmanager.initialization") + ); + } + + /// + /// Discovers and registers applications from the specified plugin. + /// + /// A context of a plugin whose applications are to be registered. + public void Register(IPluginContext pluginContext) + { + // the plugin has already been registered + if (Dictionary.ContainsKey(pluginContext)) + { + return; + } + + Dictionary.Add(pluginContext, new Dictionary()); + + var assembly = pluginContext.Assembly; + var pluginDict = Dictionary[pluginContext]; + + foreach (var type in assembly.GetExportedTypes().Where + ( + x => x.IsClass && + x.IsSealed && + x.IsPublic && + x.GetInterface(typeof(IApplication).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 = Path.DirectorySeparatorChar.ToString(); + var dataPath = Path.DirectorySeparatorChar.ToString(); + var options = new List(); + + // determining attributes + foreach (var customAttribute in type.CustomAttributes + .Where(x => x.AttributeType.GetInterfaces().Contains(typeof(IApplicationAttribute)))) + { + 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(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 + var applicationContext = new ApplicationContext + { + PluginContext = 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) + }; + + // create application + var applicationInstance = Activator.CreateInstance(type) as IApplication; + + if (!pluginDict.ContainsKey(id)) + { + pluginDict.Add(id, new ApplicationItem() + { + ApplicationContext = applicationContext, + Application = applicationInstance + }); + + HttpServerContext.Log.Debug + ( + InternationalizationManager.I18N("webexpress:applicationmanager.register", id) + ); + + // raises the AddApplication event + OnAddApplication(applicationContext); + } + else + { + HttpServerContext.Log.Warning + ( + InternationalizationManager.I18N("webexpress:applicationmanager.duplicate", id) + ); + } + } + } + + /// + /// Discovers and registers applications from the specified plugin. + /// + /// A list with plugin contexts that contain the applications. + public void Register(IEnumerable pluginContexts) + { + foreach (var pluginContext in pluginContexts) + { + Register(pluginContext); + } + } + + /// + /// Determines the application contexts for a given application id. + /// + /// The application id. + /// The context of the application or null. + public IApplicationContext GetApplcation(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 null; + } + + /// + /// Determines 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) + { + var list = new List(); + + foreach (var applicationId in applicationIds) + { + if (applicationId == "*") + { + list.AddRange(Applications); + } + else + { + list.AddRange + ( + Applications.Where + ( + x => + x.ApplicationId.Equals(applicationId, StringComparison.OrdinalIgnoreCase) || + Regex.Match(x.ApplicationId, applicationId).Success + ) + ); + } + } + + return list.Distinct(); + } + + /// + /// Determines 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) + { + if (!Dictionary.ContainsKey(pluginContext)) + { + return new List(); + } + + return Dictionary[pluginContext].Values.Select(x => x.ApplicationContext); + } + + /// + /// Boots the applications. + /// + /// The context of the plugin that contains the applications. + public void Boot(IPluginContext pluginContext) + { + if (pluginContext == null) + { + return; + } + + if (!Dictionary.ContainsKey(pluginContext)) + { + HttpServerContext.Log.Warning + ( + InternationalizationManager.I18N + ( + "webexpress:applicationmanager.application.boot.notfound", + pluginContext.PluginId + ) + ); + + return; + } + + foreach (var applicationItem in Dictionary[pluginContext]?.Values ?? Enumerable.Empty()) + { + 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 + ( + InternationalizationManager.I18N + ( + "webexpress:applicationmanager.application.processing.start", + applicationItem.ApplicationContext.ApplicationId) + ); + + applicationItem.Application.Run(); + + HttpServerContext.Log.Debug + ( + InternationalizationManager.I18N + ( + "webexpress:applicationmanager.application.processing.end", + applicationItem.ApplicationContext.ApplicationId + ) + ); + + token.ThrowIfCancellationRequested(); + }, token); + } + } + + /// + /// Shutting down applications. + /// + /// The context of the plugin that contains the applications. + public void ShutDown(IPluginContext pluginContext) + { + foreach (var applicationItem in Dictionary[pluginContext]?.Values ?? Enumerable.Empty()) + { + 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. + /// + /// The application context. + private void OnAddApplication(IApplicationContext applicationContext) + { + AddApplication?.Invoke(this, applicationContext); + } + + /// + /// Raises the RemoveApplication event. + /// + /// The application context. + private void OnRemoveApplication(IApplicationContext applicationContext) + { + RemoveApplication?.Invoke(this, applicationContext); + } + + /// + /// 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 applicationContext in GetApplcations(pluginContext)) + { + output.Add + ( + string.Empty.PadRight(deep) + + InternationalizationManager.I18N("webexpress:applicationmanager.application", applicationContext.ApplicationId) + ); + } + } + } +} diff --git a/src/WebExpress/WebApplication/IApplication.cs b/src/WebExpress/WebApplication/IApplication.cs new file mode 100644 index 0000000..ebb59e0 --- /dev/null +++ b/src/WebExpress/WebApplication/IApplication.cs @@ -0,0 +1,21 @@ +using System; + +namespace WebExpress.WebApplication +{ + /// + /// This interface represents an application. + /// + public interface IApplication : IDisposable + { + /// + /// Initialization of the application . + /// + /// The context. + void Initialization(IApplicationContext context); + + /// + /// Called when the application starts working. The call is concurrent. + /// + void Run(); + } +} diff --git a/src/WebExpress/WebApplication/IApplicationContext.cs b/src/WebExpress/WebApplication/IApplicationContext.cs new file mode 100644 index 0000000..55710e9 --- /dev/null +++ b/src/WebExpress/WebApplication/IApplicationContext.cs @@ -0,0 +1,57 @@ +using System.Collections.Generic; +using WebExpress.WebPlugin; +using WebExpress.WebUri; + +namespace WebExpress.WebApplication +{ + /// + /// The application context. + /// + public interface IApplicationContext + { + /// + /// Provides the context of the associated plugin. + /// + IPluginContext PluginContext { get; } + + /// + /// Returns the application id. + /// + string ApplicationId { get; } + + /// + /// Returns the application name. + /// + string ApplicationName { get; } + + /// + /// Provides the description. + /// + 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. + /// + string AssetPath { get; } + + /// + /// Returns the data directory. This is mounted in the data directory of the server. + /// + string DataPath { get; } + + /// + /// Returns the context path. This is mounted in the context path of the server. + /// + UriResource ContextPath { get; } + + /// + /// Returns the icon uri. + /// + UriResource Icon { get; } + } +} diff --git a/src/WebExpress/WebAttribute/ApplicationAttribute.cs b/src/WebExpress/WebAttribute/ApplicationAttribute.cs new file mode 100644 index 0000000..814e0c0 --- /dev/null +++ b/src/WebExpress/WebAttribute/ApplicationAttribute.cs @@ -0,0 +1,31 @@ +using System; +using WebExpress.WebApplication; + +namespace WebExpress.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. + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] + public class ApplicationAttribute : Attribute, IModuleAttribute where T : class, IApplication + { + + } +} diff --git a/src/WebExpress/WebAttribute/AssetPathAttribute.cs b/src/WebExpress/WebAttribute/AssetPathAttribute.cs new file mode 100644 index 0000000..f7942ed --- /dev/null +++ b/src/WebExpress/WebAttribute/AssetPathAttribute.cs @@ -0,0 +1,14 @@ +namespace WebExpress.WebAttribute +{ + public class AssetPathAttribute : System.Attribute, IApplicationAttribute, IModuleAttribute + { + /// + /// Constructor + /// + /// The path for assets. + public AssetPathAttribute(string assetPath) + { + + } + } +} diff --git a/src/WebExpress/WebAttribute/CacheAttribute.cs b/src/WebExpress/WebAttribute/CacheAttribute.cs new file mode 100644 index 0000000..c9771b0 --- /dev/null +++ b/src/WebExpress/WebAttribute/CacheAttribute.cs @@ -0,0 +1,19 @@ +using System; + +namespace WebExpress.WebAttribute +{ + /// + /// Indicates that a page or component can be reused + /// + [AttributeUsage(AttributeTargets.Class)] + public class CacheAttribute : System.Attribute, IResourceAttribute + { + /// + /// Constructor + /// + public CacheAttribute() + { + + } + } +} diff --git a/src/WebExpress/WebAttribute/ConditionAttribute.cs b/src/WebExpress/WebAttribute/ConditionAttribute.cs new file mode 100644 index 0000000..a28e9a2 --- /dev/null +++ b/src/WebExpress/WebAttribute/ConditionAttribute.cs @@ -0,0 +1,20 @@ +using System; +using WebExpress.WebCondition; + +namespace WebExpress.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 + { + /// + /// Constructor + /// + public ConditionAttribute() + { + + } + } +} diff --git a/src/WebExpress/WebAttribute/ContextPathAttribute.cs b/src/WebExpress/WebAttribute/ContextPathAttribute.cs new file mode 100644 index 0000000..c37be5c --- /dev/null +++ b/src/WebExpress/WebAttribute/ContextPathAttribute.cs @@ -0,0 +1,17 @@ +using System; + +namespace WebExpress.WebAttribute +{ + [AttributeUsage(AttributeTargets.All, AllowMultiple = false)] + public class ContextPathAttribute : Attribute, IApplicationAttribute, IModuleAttribute, IResourceAttribute + { + /// + /// Constructor + /// + /// The context path. + public ContextPathAttribute(string contetxPath) + { + + } + } +} diff --git a/src/WebExpress/WebAttribute/DataPathAttribute.cs b/src/WebExpress/WebAttribute/DataPathAttribute.cs new file mode 100644 index 0000000..8fac4ab --- /dev/null +++ b/src/WebExpress/WebAttribute/DataPathAttribute.cs @@ -0,0 +1,14 @@ +namespace WebExpress.WebAttribute +{ + public class DataPathAttribute : System.Attribute, IApplicationAttribute, IModuleAttribute + { + /// + /// Constructor + /// + /// The path for the data. + public DataPathAttribute(string dataPath) + { + + } + } +} diff --git a/src/WebExpress/WebAttribute/DefaultAttribute.cs b/src/WebExpress/WebAttribute/DefaultAttribute.cs new file mode 100644 index 0000000..5023d08 --- /dev/null +++ b/src/WebExpress/WebAttribute/DefaultAttribute.cs @@ -0,0 +1,19 @@ +using System; + +namespace WebExpress.WebAttribute +{ + /// + /// Indicates that a status page ist default + /// + [AttributeUsage(AttributeTargets.Class)] + public class DefaultAttribute : Attribute, IStatusPageAttribute + { + /// + /// Constructor + /// + public DefaultAttribute() + { + + } + } +} diff --git a/src/WebExpress/WebAttribute/DependencyAttribute.cs b/src/WebExpress/WebAttribute/DependencyAttribute.cs new file mode 100644 index 0000000..3a91320 --- /dev/null +++ b/src/WebExpress/WebAttribute/DependencyAttribute.cs @@ -0,0 +1,20 @@ +using System; + +namespace WebExpress.WebAttribute +{ + /// + /// Marks a plugin as dependent on another plugin. + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] + public class DependencyAttribute : System.Attribute, IPluginAttribute + { + /// + /// Constructor + /// + /// The Id of the plugin to which there is a dependency. + public DependencyAttribute(string dependency) + { + + } + } +} diff --git a/src/WebExpress/WebAttribute/DescriptionAttribute.cs b/src/WebExpress/WebAttribute/DescriptionAttribute.cs new file mode 100644 index 0000000..70694de --- /dev/null +++ b/src/WebExpress/WebAttribute/DescriptionAttribute.cs @@ -0,0 +1,14 @@ +namespace WebExpress.WebAttribute +{ + public class DescriptionAttribute : System.Attribute, IPluginAttribute, IApplicationAttribute, IModuleAttribute + { + /// + /// Constructor + /// + /// The description. + public DescriptionAttribute(string description) + { + + } + } +} diff --git a/src/WebExpress/WebAttribute/IApplicationAttribute.cs b/src/WebExpress/WebAttribute/IApplicationAttribute.cs new file mode 100644 index 0000000..69dc939 --- /dev/null +++ b/src/WebExpress/WebAttribute/IApplicationAttribute.cs @@ -0,0 +1,9 @@ +namespace WebExpress.WebAttribute +{ + /// + /// Interface of a application assignment attribute. + /// + public interface IApplicationAttribute + { + } +} diff --git a/src/WebExpress/WebAttribute/IModuleAttribute.cs b/src/WebExpress/WebAttribute/IModuleAttribute.cs new file mode 100644 index 0000000..46d40e5 --- /dev/null +++ b/src/WebExpress/WebAttribute/IModuleAttribute.cs @@ -0,0 +1,9 @@ +namespace WebExpress.WebAttribute +{ + /// + /// Interface of a module assignment attribute. + /// + public interface IModuleAttribute + { + } +} diff --git a/src/WebExpress/WebAttribute/IPluginAttribute.cs b/src/WebExpress/WebAttribute/IPluginAttribute.cs new file mode 100644 index 0000000..773c8af --- /dev/null +++ b/src/WebExpress/WebAttribute/IPluginAttribute.cs @@ -0,0 +1,9 @@ +namespace WebExpress.WebAttribute +{ + /// + /// Identifies a class as a plug-in component. + /// + public interface IPluginAttribute + { + } +} diff --git a/src/WebExpress/WebAttribute/IResourceAttribute.cs b/src/WebExpress/WebAttribute/IResourceAttribute.cs new file mode 100644 index 0000000..1c1ed0b --- /dev/null +++ b/src/WebExpress/WebAttribute/IResourceAttribute.cs @@ -0,0 +1,9 @@ +namespace WebExpress.WebAttribute +{ + /// + /// Interface of a resource assignment attribute. + /// + public interface IResourceAttribute + { + } +} diff --git a/src/WebExpress/WebAttribute/ISegmentAttribute.cs b/src/WebExpress/WebAttribute/ISegmentAttribute.cs new file mode 100644 index 0000000..7ed925d --- /dev/null +++ b/src/WebExpress/WebAttribute/ISegmentAttribute.cs @@ -0,0 +1,13 @@ +using WebExpress.WebUri; + +namespace WebExpress.WebAttribute +{ + public interface ISegmentAttribute + { + /// + /// Conversion to a path segment. + /// + /// The path segment. + IUriPathSegment ToPathSegment(); + } +} diff --git a/src/WebExpress/WebAttribute/IStatusPageAttribute.cs b/src/WebExpress/WebAttribute/IStatusPageAttribute.cs new file mode 100644 index 0000000..c8e16f2 --- /dev/null +++ b/src/WebExpress/WebAttribute/IStatusPageAttribute.cs @@ -0,0 +1,9 @@ +namespace WebExpress.WebAttribute +{ + /// + /// Identifies a class as a status page attribute. + /// + public interface IStatusPageAttribute + { + } +} diff --git a/src/WebExpress/WebAttribute/IconAttribute.cs b/src/WebExpress/WebAttribute/IconAttribute.cs new file mode 100644 index 0000000..157c1aa --- /dev/null +++ b/src/WebExpress/WebAttribute/IconAttribute.cs @@ -0,0 +1,14 @@ +namespace WebExpress.WebAttribute +{ + public class IconAttribute : System.Attribute, IPluginAttribute, IApplicationAttribute, IModuleAttribute + { + /// + /// Constructor + /// + /// The icon. + public IconAttribute(string icon) + { + + } + } +} diff --git a/src/WebExpress/WebAttribute/IncludeSubPathsAttribute.cs b/src/WebExpress/WebAttribute/IncludeSubPathsAttribute.cs new file mode 100644 index 0000000..f72ddfa --- /dev/null +++ b/src/WebExpress/WebAttribute/IncludeSubPathsAttribute.cs @@ -0,0 +1,17 @@ +namespace WebExpress.WebAttribute +{ + /// + /// Determines whether all resources below the specified path (including segment) are also processed. + /// + public class IncludeSubPathsAttribute : System.Attribute, IResourceAttribute + { + /// + /// Constructor + /// + /// All subpaths are included. + public IncludeSubPathsAttribute(bool includeSubPaths) + { + + } + } +} diff --git a/src/WebExpress/WebAttribute/JobAttribute.cs b/src/WebExpress/WebAttribute/JobAttribute.cs new file mode 100644 index 0000000..b5dbe52 --- /dev/null +++ b/src/WebExpress/WebAttribute/JobAttribute.cs @@ -0,0 +1,18 @@ +namespace WebExpress.WebAttribute +{ + public class JobAttribute : System.Attribute + { + /// + /// Constructor + /// + /// 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. + /// The day 1-31 or * for any. Comma-separated values or ranges (-) are also possible. + /// The month 1-12 or * for any. Comma-separated values or ranges (-) are also possible. + /// The weekday 0-6 (Sunday-Saturday) or * for any. Comma-separated values or ranges (-) are also possible. + public JobAttribute(string minute = "*", string hour = "*", string day = "*", string month = "*", string weekday = "*") + { + + } + } +} diff --git a/src/WebExpress/WebAttribute/ModuleAttribute.cs b/src/WebExpress/WebAttribute/ModuleAttribute.cs new file mode 100644 index 0000000..7822288 --- /dev/null +++ b/src/WebExpress/WebAttribute/ModuleAttribute.cs @@ -0,0 +1,21 @@ +using System; +using WebExpress.WebModule; + +namespace WebExpress.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/WebAttribute/NameAttribute.cs b/src/WebExpress/WebAttribute/NameAttribute.cs new file mode 100644 index 0000000..b3c858d --- /dev/null +++ b/src/WebExpress/WebAttribute/NameAttribute.cs @@ -0,0 +1,14 @@ +namespace WebExpress.WebAttribute +{ + public class NameAttribute : System.Attribute, IPluginAttribute, IApplicationAttribute, IModuleAttribute + { + /// + /// Constructor + /// + /// Der Name + public NameAttribute(string name) + { + + } + } +} diff --git a/src/WebExpress/WebAttribute/OptionAttribute.cs b/src/WebExpress/WebAttribute/OptionAttribute.cs new file mode 100644 index 0000000..276a706 --- /dev/null +++ b/src/WebExpress/WebAttribute/OptionAttribute.cs @@ -0,0 +1,42 @@ +using System; +using WebExpress.WebModule; +using WebExpress.WebResource; + +namespace WebExpress.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/WebAttribute/OptionalAttribute.cs b/src/WebExpress/WebAttribute/OptionalAttribute.cs new file mode 100644 index 0000000..ad320f0 --- /dev/null +++ b/src/WebExpress/WebAttribute/OptionalAttribute.cs @@ -0,0 +1,19 @@ +using System; + +namespace WebExpress.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/WebAttribute/ParentAttribute.cs b/src/WebExpress/WebAttribute/ParentAttribute.cs new file mode 100644 index 0000000..9f0a332 --- /dev/null +++ b/src/WebExpress/WebAttribute/ParentAttribute.cs @@ -0,0 +1,17 @@ +using System; +using WebExpress.WebResource; + +namespace WebExpress.WebAttribute +{ + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] + public class ParentAttribute : Attribute, IResourceAttribute where T : class, IResource + { + /// + /// Constructor + /// + public ParentAttribute() + { + + } + } +} diff --git a/src/WebExpress/WebAttribute/ScopeAttribute.cs b/src/WebExpress/WebAttribute/ScopeAttribute.cs new file mode 100644 index 0000000..d80a049 --- /dev/null +++ b/src/WebExpress/WebAttribute/ScopeAttribute.cs @@ -0,0 +1,20 @@ +using System; +using WebExpress.WebScope; + +namespace WebExpress.WebAttribute +{ + /// + /// The range in which the component is valid. + /// + [AttributeUsage(AttributeTargets.All, AllowMultiple = true)] + public class ScopeAttribute : Attribute, IResourceAttribute where T : class, IScope + { + /// + /// Constructor + /// + public ScopeAttribute() + { + + } + } +} diff --git a/src/WebExpress/WebAttribute/SegmentAttribute.cs b/src/WebExpress/WebAttribute/SegmentAttribute.cs new file mode 100644 index 0000000..3a70053 --- /dev/null +++ b/src/WebExpress/WebAttribute/SegmentAttribute.cs @@ -0,0 +1,42 @@ +using System; +using WebExpress.WebUri; + +namespace WebExpress.WebAttribute +{ + /// + /// A static path segment. + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] + public class SegmentAttribute : Attribute, IResourceAttribute, ISegmentAttribute + { + /// + /// Returns or set the segment of the uri path. + /// + private string Segment { get; set; } + + /// + /// Returns or sets the display string. + /// + private string Display { get; set; } + + /// + /// Constructor + /// + /// The segment of the uri path. + /// The display string. + public SegmentAttribute(string segment, string display = null) + { + Segment = segment; + Display = display; + } + + /// + /// Conversion to a path segment. + /// + /// The path segment. + public IUriPathSegment ToPathSegment() + { + return new UriPathSegmentConstant(Segment, Display) { }; + } + } +} diff --git a/src/WebExpress/WebAttribute/SegmentDoubleAttribute.cs b/src/WebExpress/WebAttribute/SegmentDoubleAttribute.cs new file mode 100644 index 0000000..349cc6d --- /dev/null +++ b/src/WebExpress/WebAttribute/SegmentDoubleAttribute.cs @@ -0,0 +1,50 @@ +using System; +using WebExpress.WebUri; + +namespace WebExpress.WebAttribute +{ + public class SegmentDoubleAttribute : Attribute, IResourceAttribute, ISegmentAttribute + { + /// + /// Returns or sets the name of the variable. + /// + private string VariableName { get; set; } + + /// + /// Returns or sets the display string. + /// + private string Display { get; set; } + + /// + /// Constructor + /// + /// The name of the variable. + /// The display string. + public SegmentDoubleAttribute(string variableName, string display) + { + VariableName = variableName; + Display = display; + } + + /// + /// Conversion to a path segment. + /// + /// 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/WebAttribute/SegmentGuidAttribute.cs b/src/WebExpress/WebAttribute/SegmentGuidAttribute.cs new file mode 100644 index 0000000..adc9faf --- /dev/null +++ b/src/WebExpress/WebAttribute/SegmentGuidAttribute.cs @@ -0,0 +1,50 @@ +using System; +using WebExpress.WebMessage; +using WebExpress.WebUri; + +namespace WebExpress.WebAttribute +{ + /// + /// A dynamic path segment of type guid. + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] + public class SegmentGuidAttribute : Attribute, IResourceAttribute, ISegmentAttribute where T : Parameter + { + /// + /// Returns or sets the name of the variable. + /// + private string VariableName { get; set; } + + /// + /// Returns or sets the display string. + /// + private string Display { get; set; } + + /// + /// Returns or sets the display format. + /// + private UriPathSegmentVariableGuid.Format DisplayFormat { get; set; } + + /// + /// Constructor + /// + /// 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(); + Display = display; + DisplayFormat = displayFormat; + } + + /// + /// Conversion to a path segment. + /// + /// The path segment. + public IUriPathSegment ToPathSegment() + { + return new UriPathSegmentVariableGuid(VariableName, Display, DisplayFormat); + } + } +} diff --git a/src/WebExpress/WebAttribute/SegmentIntAttribute.cs b/src/WebExpress/WebAttribute/SegmentIntAttribute.cs new file mode 100644 index 0000000..52129d6 --- /dev/null +++ b/src/WebExpress/WebAttribute/SegmentIntAttribute.cs @@ -0,0 +1,38 @@ +using System; +using WebExpress.WebUri; + +namespace WebExpress.WebAttribute +{ + public class SegmentIntAttribute : Attribute, IResourceAttribute, ISegmentAttribute + { + /// + /// Returns or sets the name of the variable. + /// + private string VariableName { get; set; } + + /// + /// Returns or sets the display string. + /// + private string Display { get; set; } + + /// + /// Constructor + /// + /// The name of the variable. + /// The display string. + public SegmentIntAttribute(string variableName, string display) + { + VariableName = variableName; + Display = display; + } + + /// + /// Conversion to a path segment. + /// + /// The path segment. + public IUriPathSegment ToPathSegment() + { + return new UriPathSegmentVariableInt(VariableName, Display); + } + } +} diff --git a/src/WebExpress/WebAttribute/SegmentStringAttribute.cs b/src/WebExpress/WebAttribute/SegmentStringAttribute.cs new file mode 100644 index 0000000..0232e46 --- /dev/null +++ b/src/WebExpress/WebAttribute/SegmentStringAttribute.cs @@ -0,0 +1,50 @@ +using System; +using WebExpress.WebUri; + +namespace WebExpress.WebAttribute +{ + public class SegmentStringAttribute : Attribute, IResourceAttribute, ISegmentAttribute + { + /// + /// Returns or sets the name of the variable. + /// + private string VariableName { get; set; } + + /// + /// Returns or sets the display string. + /// + private string Display { get; set; } + + /// + /// Constructor + /// + /// The name of the variable. + /// The display string. + public SegmentStringAttribute(string variableName, string display) + { + VariableName = variableName; + Display = display; + } + + /// + /// Conversion to a path segment. + /// + /// 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/WebAttribute/SegmentUIntAttribute.cs b/src/WebExpress/WebAttribute/SegmentUIntAttribute.cs new file mode 100644 index 0000000..d6c6dfc --- /dev/null +++ b/src/WebExpress/WebAttribute/SegmentUIntAttribute.cs @@ -0,0 +1,55 @@ +using System; +using WebExpress.WebUri; + +namespace WebExpress.WebAttribute +{ + public class SegmentUIntAttribute : Attribute, IResourceAttribute, ISegmentAttribute + { + /// + /// Returns or sets the name of the variable. + /// + private string VariableName { get; set; } + + /// + /// Returns or sets the display string. + /// + private string Display { get; set; } + + /// + /// Constructor + /// + /// The name of the variable. + /// The display string. + public SegmentUIntAttribute(string variableName, string display) + { + VariableName = variableName; + Display = display; + } + + /// + /// Conversion to a path segment. + /// + /// 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/WebAttribute/StatusCodeAttribute.cs b/src/WebExpress/WebAttribute/StatusCodeAttribute.cs new file mode 100644 index 0000000..ec9c926 --- /dev/null +++ b/src/WebExpress/WebAttribute/StatusCodeAttribute.cs @@ -0,0 +1,17 @@ +using System; + +namespace WebExpress.WebAttribute +{ + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] + public class StatusCodeAttribute : System.Attribute, IApplicationAttribute + { + /// + /// Constructor + /// + /// The status code. + public StatusCodeAttribute(int status) + { + + } + } +} diff --git a/src/WebExpress/WebAttribute/SystemPluginAttribute.cs b/src/WebExpress/WebAttribute/SystemPluginAttribute.cs new file mode 100644 index 0000000..7354721 --- /dev/null +++ b/src/WebExpress/WebAttribute/SystemPluginAttribute.cs @@ -0,0 +1,19 @@ +using System; + +namespace WebExpress.WebAttribute +{ + /// + /// Marks an assembly as systemically relevant. + /// + [AttributeUsage(AttributeTargets.Assembly)] + public class SystemPluginAttribute : Attribute + { + /// + /// Constructor + /// + public SystemPluginAttribute() + { + + } + } +} diff --git a/src/WebExpress/WebAttribute/TitleAttribute.cs b/src/WebExpress/WebAttribute/TitleAttribute.cs new file mode 100644 index 0000000..830bb2f --- /dev/null +++ b/src/WebExpress/WebAttribute/TitleAttribute.cs @@ -0,0 +1,14 @@ +namespace WebExpress.WebAttribute +{ + public class TitleAttribute : System.Attribute, IResourceAttribute + { + /// + /// Constructor + /// + /// The display text. + public TitleAttribute(string display) + { + + } + } +} diff --git a/src/WebExpress/WebComponent/ComponentDictionary.cs b/src/WebExpress/WebComponent/ComponentDictionary.cs new file mode 100644 index 0000000..99c0da5 --- /dev/null +++ b/src/WebExpress/WebComponent/ComponentDictionary.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; +using WebExpress.WebPlugin; + +namespace WebExpress.WebComponent +{ + /// + /// Internal management of components. + /// key = plugin + /// value = component item + /// + public class ComponentDictionary : Dictionary> + { + + } +} diff --git a/src/WebExpress/WebComponent/ComponentItem.cs b/src/WebExpress/WebComponent/ComponentItem.cs new file mode 100644 index 0000000..e5f1d98 --- /dev/null +++ b/src/WebExpress/WebComponent/ComponentItem.cs @@ -0,0 +1,30 @@ +using System; + +namespace WebExpress.WebComponent +{ + public class ComponentItem + { + /// + /// Returns or set the class type for a component. + /// + public Type ComponentClass { get; internal set; } + + /// + /// Returns the component id. + /// + public string ComponentId { get; internal set; } + + /// + /// Returns the component instance or null if not already created. + /// + public IComponent ComponentInstance { get; internal set; } + + /// + /// Constructor + /// + internal ComponentItem() + { + + } + } +} diff --git a/src/WebExpress/WebComponent/ComponentManager.cs b/src/WebExpress/WebComponent/ComponentManager.cs new file mode 100644 index 0000000..69e7c1c --- /dev/null +++ b/src/WebExpress/WebComponent/ComponentManager.cs @@ -0,0 +1,488 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using WebExpress.Internationalization; +using WebExpress.WebApplication; +using WebExpress.WebEvent; +using WebExpress.WebJob; +using WebExpress.WebModule; +using WebExpress.WebPackage; +using WebExpress.WebPlugin; +using WebExpress.WebResource; +using WebExpress.WebSession; +using WebExpress.WebSitemap; +using WebExpress.WebStatusPage; +using WebExpress.WebTask; + +namespace WebExpress.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/WebComponent/IComponent.cs b/src/WebExpress/WebComponent/IComponent.cs new file mode 100644 index 0000000..6ca4d05 --- /dev/null +++ b/src/WebExpress/WebComponent/IComponent.cs @@ -0,0 +1,30 @@ +using System.Collections.Generic; +using WebExpress.WebPlugin; + +namespace WebExpress.WebComponent +{ + /// + /// Interface of the manager classes. + /// + 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/WebComponent/IComponentPlugin.cs b/src/WebExpress/WebComponent/IComponentPlugin.cs new file mode 100644 index 0000000..b86304e --- /dev/null +++ b/src/WebExpress/WebComponent/IComponentPlugin.cs @@ -0,0 +1,29 @@ +using System.Collections.Generic; +using WebExpress.WebPlugin; + +namespace WebExpress.WebComponent +{ + /// + /// Interface of the manager classes. + /// + public interface IComponentPlugin : IComponent + { + /// + /// Discovers and registers entries from the specified plugin. + /// + /// A context of a plugin whose elements are to be registered. + void Register(IPluginContext pluginContext); + + /// + /// Discovers and registers entries from the specified plugin. + /// + /// A list with plugin contexts that contain the elements. + void Register(IEnumerable pluginContexts); + + /// + /// Removes all elemets associated with the specified plugin context. + /// + /// The context of the plugin that contains the elemets to remove. + void Remove(IPluginContext pluginContext); + } +} diff --git a/src/WebExpress/WebComponent/IExecutableElements.cs b/src/WebExpress/WebComponent/IExecutableElements.cs new file mode 100644 index 0000000..a298abb --- /dev/null +++ b/src/WebExpress/WebComponent/IExecutableElements.cs @@ -0,0 +1,28 @@ +using WebExpress.WebPlugin; + +namespace WebExpress.WebComponent +{ + /// + /// Indicates that a manager manages executable elements. + /// + public interface IExecutableElements + { + /// + /// Boots the executable elements of a plugin. + /// + /// The context of the plugin containing the elements. + internal void Boot(IPluginContext pluginContext) + { + + } + + /// + /// Terminate the executable elements of a plugin. + /// + /// The context of the plugin containing the elements. + public void ShutDown(IPluginContext pluginContext) + { + + } + } +} diff --git a/src/WebExpress/WebComponent/ISystemComponent.cs b/src/WebExpress/WebComponent/ISystemComponent.cs new file mode 100644 index 0000000..5e686b6 --- /dev/null +++ b/src/WebExpress/WebComponent/ISystemComponent.cs @@ -0,0 +1,9 @@ +namespace WebExpress.WebComponent +{ + /// + /// Marks a manager as systemically relevant. + /// + internal interface ISystemComponent + { + } +} diff --git a/src/WebExpress/WebCondition/ICondition.cs b/src/WebExpress/WebCondition/ICondition.cs new file mode 100644 index 0000000..348ddf0 --- /dev/null +++ b/src/WebExpress/WebCondition/ICondition.cs @@ -0,0 +1,17 @@ +using WebExpress.WebMessage; + +namespace WebExpress.WebCondition +{ + /// + /// Stellt eine Bedingiung dar, die erfüllt sein muss + /// + public interface ICondition + { + /// + /// Die Bedingung + /// + /// The request. + /// true wenn die Bedingung erfüllt ist, false sonst + bool Fulfillment(Request request); + } +} diff --git a/src/WebExpress/WebEvent/EventDictionary.cs b/src/WebExpress/WebEvent/EventDictionary.cs new file mode 100644 index 0000000..a579885 --- /dev/null +++ b/src/WebExpress/WebEvent/EventDictionary.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; +using WebExpress.WebEvent; +using WebExpress.WebPlugin; + +namespace WebExpress.WebJob +{ + /// + /// key = plugin context + /// value = ressource items + /// + internal class EventDictionary : Dictionary> + { + } +} diff --git a/src/WebExpress/WebEvent/EventManager.cs b/src/WebExpress/WebEvent/EventManager.cs new file mode 100644 index 0000000..1446b8c --- /dev/null +++ b/src/WebExpress/WebEvent/EventManager.cs @@ -0,0 +1,174 @@ +using System.Collections.Generic; +using System.Linq; +using WebExpress.Internationalization; +using WebExpress.WebComponent; +using WebExpress.WebJob; +using WebExpress.WebModule; +using WebExpress.WebPlugin; + +namespace WebExpress.WebEvent +{ + /// + /// The event manager. + /// + public sealed class EventManager : IComponentPlugin, ISystemComponent + { + /// + /// Returns or sets the reference to the context of the host. + /// + public IHttpServerContext HttpServerContext { get; private set; } + + /// + /// Returns the directory where the events are listed. + /// + private EventDictionary Dictionary { get; } = new EventDictionary(); + + /// + /// Constructor + /// + internal EventManager() + { + ComponentManager.PluginManager.AddPlugin += (sender, pluginContext) => + { + Register(pluginContext); + }; + + ComponentManager.PluginManager.RemovePlugin += (sender, pluginContext) => + { + Remove(pluginContext); + }; + + ComponentManager.ModuleManager.AddModule += (sender, moduleContext) => + { + AssignToModule(moduleContext); + }; + + ComponentManager.ModuleManager.RemoveModule += (sender, moduleContext) => + { + DetachFromModule(moduleContext); + }; + } + + /// + /// Initialization + /// + /// The reference to the context of the host. + public void Initialization(IHttpServerContext context) + { + HttpServerContext = context; + + HttpServerContext.Log.Debug + ( + InternationalizationManager.I18N + ( + "webexpress:eventmanager.initialization" + ) + ); + } + + /// + /// Discovers and registers event handlers from the specified plugin. + /// + /// A context of a plugin whose event handlers are to be registered. + public void Register(IPluginContext pluginContext) + { + var assembly = pluginContext?.Assembly; + + foreach (var eventHandlerType in assembly.GetTypes().Where + ( + x => x.IsClass == true && + x.IsSealed && + x.IsPublic && + x.GetInterface(typeof(IEventHandler).Name) != null + )) + { + + } + } + + /// + /// Discovers and registers entries from the specified plugin. + /// + /// A list with plugin contexts that contain the jobs. + public void Register(IEnumerable pluginContexts) + { + foreach (var pluginContext in pluginContexts) + { + Register(pluginContext); + } + } + + /// + /// Assign existing event to the module. + /// + /// The context of the module. + private void AssignToModule(IModuleContext moduleContext) + { + //foreach (var scheduleItem in Dictionary.Values.SelectMany(x => x)) + //{ + // if (scheduleItem.moduleId.Equals(moduleContext?.ModuleId)) + // { + // scheduleItem.AddModule(moduleContext); + // } + //} + } + + /// + /// Remove an existing modules to the event. + /// + /// The context of the module. + private void DetachFromModule(IModuleContext moduleContext) + { + //foreach (var scheduleItem in Dictionary.Values.SelectMany(x => x)) + //{ + // if (scheduleItem.moduleId.Equals(moduleContext?.ModuleId)) + // { + // scheduleItem.DetachModule(moduleContext); + // } + //} + } + + /// + /// Removes all jobs associated with the specified plugin context. + /// + /// The context of the plugin that contains the event to remove. + public void Remove(IPluginContext pluginContext) + { + //// the plugin has not been registered in the manager + //if (!Dictionary.ContainsKey(pluginContext)) + //{ + // return; + //} + + //foreach (var scheduleItem in Dictionary[pluginContext]) + //{ + // scheduleItem.Dispose(); + //} + + //Dictionary.Remove(pluginContext); + } + + /// + /// Information about the component is collected and prepared for output in the event. + /// + /// 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:eventmanager.job", + // scheduleItem.JobId, + // scheduleItem.ModuleContext + // ) + // ); + //} + } + } +} diff --git a/src/WebExpress/WebEvent/IEventContext.cs b/src/WebExpress/WebEvent/IEventContext.cs new file mode 100644 index 0000000..7008149 --- /dev/null +++ b/src/WebExpress/WebEvent/IEventContext.cs @@ -0,0 +1,18 @@ +using WebExpress.WebModule; +using WebExpress.WebPlugin; + +namespace WebExpress.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/WebEvent/IEventHandler.cs b/src/WebExpress/WebEvent/IEventHandler.cs new file mode 100644 index 0000000..4abe972 --- /dev/null +++ b/src/WebExpress/WebEvent/IEventHandler.cs @@ -0,0 +1,6 @@ +namespace WebExpress.WebEvent +{ + public interface IEventHandler + { + } +} diff --git a/src/WebExpress/WebEx.cs b/src/WebExpress/WebEx.cs new file mode 100644 index 0000000..1d5f6ab --- /dev/null +++ b/src/WebExpress/WebEx.cs @@ -0,0 +1,275 @@ +using System; +using System.Globalization; +using System.IO; +using System.Reflection; +using System.Threading; +using System.Xml.Serialization; +using WebExpress.Config; +using WebExpress.WebComponent; +using WebExpress.WebUri; +using static WebExpress.Internationalization.InternationalizationManager; + +namespace WebExpress +{ + public class WebEx + { + /// + /// 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. + /// + /// Call arguments. + public static int Main(string[] args) + { + var app = new WebEx() + { + Name = Assembly.GetExecutingAssembly().GetName().Name + }; + + return app.Execution(args); + } + + /// + /// Running the application. + /// + /// Call arguments. + public int Execution(string[] args) + { + // prepare call arguments + ArgumentParser.Current.Register(new ArgumentParserCommand() { FullName = "help", ShortName = "h" }); + ArgumentParser.Current.Register(new ArgumentParserCommand() { FullName = "config", ShortName = "c" }); + ArgumentParser.Current.Register(new ArgumentParserCommand() { FullName = "port", ShortName = "p" }); + ArgumentParser.Current.Register(new ArgumentParserCommand() { FullName = "spec", ShortName = "s" }); + ArgumentParser.Current.Register(new ArgumentParserCommand() { FullName = "output", ShortName = "o" }); + ArgumentParser.Current.Register(new ArgumentParserCommand() { FullName = "target", ShortName = "t" }); + + // parsing call arguments + var argumentDict = ArgumentParser.Current.Parse(args); + + if (argumentDict.ContainsKey("help")) + { + Console.WriteLine(Name + " [-port number | -config dateiname | -help]"); + Console.WriteLine("Version: " + Version); + + return 0; + } + + // package builder + if (argumentDict.ContainsKey("spec") || argumentDict.ContainsKey("output")) + { + if (!argumentDict.ContainsKey("spec")) + { + Console.WriteLine("*** PackageBuilder: The spec file (-s) was not specified."); + + return 1; + } + + if (!argumentDict.ContainsKey("config")) + { + Console.WriteLine("*** PackageBuilder: The config (-c) was not specified."); + + return 1; + } + + if (!argumentDict.ContainsKey("target")) + { + Console.WriteLine("*** PackageBuilder: The target framework (-t) was not specified."); + + return 1; + } + + if (!argumentDict.ContainsKey("output")) + { + Console.WriteLine("*** PackageBuilder: The output directory (-o) was not specified."); + + return 1; + } + + WebPackage.PackageBuilder.Create(argumentDict["spec"], argumentDict["config"], argumentDict["target"], argumentDict["output"]); + + return 0; + } + + // configuration + if (!argumentDict.ContainsKey("config")) + { + // check if there is a file called config.xml + if (!File.Exists(Path.Combine(Path.Combine(Environment.CurrentDirectory, "config"), "webexpress.config.xml"))) + { + Console.WriteLine("No configuration file was specified. Usage: " + Name + " -config filename"); + + return 1; + } + + argumentDict.Add("config", "webexpress.config.xml"); + } + + // initialization of the web server + Initialization(ArgumentParser.Current.GetValidArguments(args), Path.Combine(Path.Combine(Environment.CurrentDirectory, "config"), argumentDict["config"])); + + // start the manager + ComponentManager.Execute(); + + // starting the web server + Start(); + + // finish + Exit(); + + return 0; + } + + /// + /// Called when the application is to be terminated using Ctrl+C. + /// + /// The trigger of the event. + /// The event argument. + private void OnCancel(object sender, ConsoleCancelEventArgs e) + { + Exit(); + } + + /// + /// Initialization + /// + /// The valid arguments. + /// The configuration file. + private void Initialization(string args, string configFile) + { + // Config laden + using var reader = new FileStream(configFile, FileMode.Open); + var serializer = new XmlSerializer(typeof(HttpServerConfig)); + var config = serializer.Deserialize(reader) as HttpServerConfig; + var log = new Log(); + + var culture = CultureInfo.CurrentCulture; + + try + { + culture = new CultureInfo(config.Culture); + + CultureInfo.CurrentCulture = culture; + } + catch + { + + } + + var packageBase = string.IsNullOrWhiteSpace(config.PackageBase) ? + Environment.CurrentDirectory : Path.IsPathRooted(config.PackageBase) ? + config.PackageBase : + Path.Combine(Environment.CurrentDirectory, config.PackageBase); + + var assetBase = string.IsNullOrWhiteSpace(config.AssetBase) ? + Environment.CurrentDirectory : Path.IsPathRooted(config.AssetBase) ? + config.AssetBase : + Path.Combine(Environment.CurrentDirectory, config.AssetBase); + + var dataBase = string.IsNullOrWhiteSpace(config.DataBase) ? + Environment.CurrentDirectory : Path.IsPathRooted(config.DataBase) ? + config.DataBase : + Path.Combine(Environment.CurrentDirectory, config.DataBase); + + var context = new HttpServerContext + ( + config.Uri, + config.Endpoints, + Path.GetFullPath(packageBase), + Path.GetFullPath(assetBase), + Path.GetFullPath(dataBase), + Path.GetDirectoryName(configFile), + new UriResource(config.ContextPath), + culture, + log, + null + ); + + HttpServer = new HttpServer(context) + { + Config = config + }; + + // start logging + HttpServer.HttpServerContext.Log.Begin(config.Log); + + // log program start + HttpServer.HttpServerContext.Log.Seperator('/'); + HttpServer.HttpServerContext.Log.Info(message: I18N("webexpress:app.startup")); + HttpServer.HttpServerContext.Log.Info(message: "".PadRight(80, '-')); + HttpServer.HttpServerContext.Log.Info(message: I18N("webexpress:app.version"), args: Version); + HttpServer.HttpServerContext.Log.Info(message: I18N("webexpress:app.arguments"), args: args); + HttpServer.HttpServerContext.Log.Info(message: I18N("webexpress:app.workingdirectory"), args: Environment.CurrentDirectory); + HttpServer.HttpServerContext.Log.Info(message: I18N("webexpress:app.packagebase"), args: config.PackageBase); + HttpServer.HttpServerContext.Log.Info(message: I18N("webexpress:app.assetbase"), args: config.AssetBase); + HttpServer.HttpServerContext.Log.Info(message: I18N("webexpress:app.database"), args: config.DataBase); + HttpServer.HttpServerContext.Log.Info(message: I18N("webexpress:app.configurationdirectory"), args: Path.GetDirectoryName(configFile)); + HttpServer.HttpServerContext.Log.Info(message: I18N("webexpress:app.configuration"), args: Path.GetFileName(configFile)); + HttpServer.HttpServerContext.Log.Info(message: I18N("webexpress:app.logdirectory"), args: Path.GetDirectoryName(HttpServer.HttpServerContext.Log.Filename)); + HttpServer.HttpServerContext.Log.Info(message: I18N("webexpress:app.log"), args: Path.GetFileName(HttpServer.HttpServerContext.Log.Filename)); + foreach (var v in config.Endpoints) + { + HttpServer.HttpServerContext.Log.Info(message: I18N("webexpress:app.uri"), args: v.Uri); + } + + HttpServer.HttpServerContext.Log.Seperator('='); + + if (!Directory.Exists(config.PackageBase)) + { + Directory.CreateDirectory(config.PackageBase); + } + + if (!Directory.Exists(config.AssetBase)) + { + Directory.CreateDirectory(config.AssetBase); + } + + if (!Directory.Exists(config.DataBase)) + { + Directory.CreateDirectory(config.DataBase); + } + + Console.CancelKeyPress += OnCancel; + } + + /// + /// Start the web server. + /// + private void Start() + { + HttpServer.Start(); + + Thread.CurrentThread.Join(); + } + + /// + /// Quits the application. + /// + private void Exit() + { + HttpServer.Stop(); + + // end of program log + HttpServer.HttpServerContext.Log.Seperator('='); + HttpServer.HttpServerContext.Log.Info(message: I18N("webexpress:app.errors"), args: HttpServer.HttpServerContext.Log.ErrorCount); + HttpServer.HttpServerContext.Log.Info(message: I18N("webexpress:app.warnings"), args: HttpServer.HttpServerContext.Log.WarningCount); + HttpServer.HttpServerContext.Log.Info(message: I18N("webexpress:app.done")); + HttpServer.HttpServerContext.Log.Seperator('/'); + + // stop logging + HttpServer.HttpServerContext.Log.Close(); + } + } +} diff --git a/src/WebExpress/WebExpress.csproj b/src/WebExpress/WebExpress.csproj new file mode 100644 index 0000000..a4a44f8 --- /dev/null +++ b/src/WebExpress/WebExpress.csproj @@ -0,0 +1,59 @@ + + + + Library + WebExpress + 0.0.1.0 + 0.0.1.0 + net7.0 + any + https://github.com/ReneSchwarzer/WebExpress.git + Rene_Schwarzer@hotmail.de + MIT + Rene_Schwarzer@hotmail.de + true + True + Core library of the WebExpress web server. + 0.0.1-alpha + https://github.com/ReneSchwarzer/WebExpress + icon.png + README.md + git + webexpress + True + true + + + + + + + + + + + + + + + + + + + True + \ + + + True + \ + + + + + + True + \ + + + + diff --git a/src/WebExpress/WebHtml/Css.cs b/src/WebExpress/WebHtml/Css.cs new file mode 100644 index 0000000..233e9a1 --- /dev/null +++ b/src/WebExpress/WebHtml/Css.cs @@ -0,0 +1,28 @@ +using System.Linq; + +namespace WebExpress.WebHtml +{ + public static class Css + { + /// + /// Verbindet die angebenen CSS-Klassen zu einem String + /// + /// Die einzelnen CSS-Klassen + /// Die Css-Klassen als String + public static string Concatenate(params string[] items) + { + return string.Join(' ', items.Where(x => !string.IsNullOrWhiteSpace(x)).Distinct()); + } + + /// + /// Entfernt die angegebenen CSS-Klassen aus dem Gesammtsring + /// + /// Die in einem gemeinsamen String verbundenen CSS-Klassen + /// Die zu entfernenden CSS-Klassen + /// Die Css-Klassen als String + public static string Remove(string css, params string[] remove) + { + return string.Join(' ', css.Split(' ').Where(x => !remove.Contains(x))); + } + } +} diff --git a/src/WebExpress/WebHtml/Favicon.cs b/src/WebExpress/WebHtml/Favicon.cs new file mode 100644 index 0000000..65e89c7 --- /dev/null +++ b/src/WebExpress/WebHtml/Favicon.cs @@ -0,0 +1,80 @@ +namespace WebExpress.WebHtml +{ + public class Favicon + { + /// + /// Returns or sets the uri. + /// + public string Url { get; set; } + + /// + /// Returns or sets the media type. + /// + public TypeFavicon Mediatype { get; set; } + + /// + /// Constructor + /// + /// The uri. + /// The media type. + public Favicon(string url, TypeFavicon mediatype) + { + Url = url; + Mediatype = mediatype; + } + + /// + /// Constructor + /// + /// The uri. + /// The media type. + public Favicon(string url) + { + Url = url; + } + + /// + /// Constructor + /// + /// The uri. + /// The media type. + public Favicon(string url, string mediatype) + { + Url = url; + + switch (mediatype) + { + case "image/x-icon": + Mediatype = TypeFavicon.ICON; + break; + case "image/jpg": + Mediatype = TypeFavicon.JPG; + break; + case "image/png": + Mediatype = TypeFavicon.PNG; + break; + case "image/svg+xml": + Mediatype = TypeFavicon.SVG; + break; + default: + break; + } + } + + /// + /// Returns the media type. + /// + /// The media type. + public string GetMediatyp() + { + return Mediatype switch + { + TypeFavicon.ICON => "image/x-icon", + TypeFavicon.JPG => "image/jpg", + TypeFavicon.PNG => "image/png", + TypeFavicon.SVG => "image/svg+xml", + _ => "", + }; + } + } +} diff --git a/src/WebExpress/WebHtml/HTMLElementExtension.cs b/src/WebExpress/WebHtml/HTMLElementExtension.cs new file mode 100644 index 0000000..d3162cc --- /dev/null +++ b/src/WebExpress/WebHtml/HTMLElementExtension.cs @@ -0,0 +1,122 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace WebExpress.WebHtml +{ + /// + /// Extension methods for HTMLEelements. + /// + public static class HTMLElementExtension + { + /// + /// Adds a css class. + /// + /// The HTML element to extend. + /// The class to add. + /// The HTML element extended by the checkout. + public static IHtmlNode AddClass(this IHtmlNode html, string cssClass) + { + if (html is HtmlElement) + { + var element = html as HtmlElement; + + var list = new List(element.Class.Split(" ".ToCharArray(), StringSplitOptions.RemoveEmptyEntries)).Select(x => x.ToLower()).ToList(); + + if (!list.Contains(cssClass.ToLower())) + { + list.Add(cssClass.ToLower()); + } + + var css = string.Join(' ', list); + + element.Class = css; + } + + return html; + } + + /// + /// Removes a css class. + /// + /// The HTML element. + /// The class to remove. + /// The HTML element reduced by the checkout. + public static IHtmlNode RemoveClass(this IHtmlNode html, string cssClass) + { + if (cssClass == null) return html; + + if (html is HtmlElement) + { + var element = html as HtmlElement; + + var list = new List(element.Class.Split(" ".ToCharArray(), StringSplitOptions.RemoveEmptyEntries)).Select(x => x.ToLower()).ToList(); + + if (list.Contains(cssClass.ToLower())) + { + list.Remove(cssClass.ToLower()); + } + + var css = string.Join(' ', list); + + element.Class = css; + } + + return html; + } + + /// + /// Adds a style. + /// + /// The HTML element to extend. + /// The class to add. + /// The HTML element extended by the checkout. + public static IHtmlNode AddStyle(this IHtmlNode html, string cssStyle) + { + if (html is HtmlElement) + { + var element = html as HtmlElement; + + var list = new List(element.Style.Split(" ".ToCharArray(), StringSplitOptions.RemoveEmptyEntries)).Select(x => x.ToLower()).ToList(); + + if (!list.Contains(cssStyle.ToLower())) + { + list.Add(cssStyle.ToLower()); + } + + var css = string.Join(' ', list); + + element.Style = css; + } + + return html; + } + + /// + /// Removes a style. + /// + /// The HTML element. + /// Der Style, welcher entfernt werden soll + /// The HTML element reduced by the checkout. + public static IHtmlNode RemoveStyle(this IHtmlNode html, string cssStyle) + { + if (html is HtmlElement) + { + var element = html as HtmlElement; + + var list = new List(element.Style.Split(" ".ToCharArray(), StringSplitOptions.RemoveEmptyEntries)).Select(x => x.ToLower()).ToList(); + + if (list.Contains(cssStyle?.ToLower())) + { + list.Remove(cssStyle.ToLower()); + } + + var css = string.Join(' ', list); + + element.Style = css; + } + + return html; + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlAttribute.cs b/src/WebExpress/WebHtml/HtmlAttribute.cs new file mode 100644 index 0000000..6ca999a --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlAttribute.cs @@ -0,0 +1,58 @@ +using System.Text; + +namespace WebExpress.WebHtml +{ + public class HtmlAttribute : IHtmlAttribute + { + /// + /// Returns or sets the name. des Attributes + /// + public string Name { get; set; } + + /// + /// Returns or sets the value. + /// + public string Value { get; set; } + + /// + /// Constructor + /// + public HtmlAttribute() + { + + } + + /// + /// Constructor + /// + /// Der Name + public HtmlAttribute(string name) + { + Name = name; + } + + /// + /// Constructor + /// + /// Der Name + /// The value. + public HtmlAttribute(string name, string value) + { + Name = name; + Value = value; + } + + /// + /// Convert to a string using a StringBuilder. + /// + /// The string builder. + /// The call depth. + public virtual void ToString(StringBuilder builder, int deep) + { + builder.Append(Name); + builder.Append("=\""); + builder.Append(Value); + builder.Append("\""); + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlAttributeNoneValue.cs b/src/WebExpress/WebHtml/HtmlAttributeNoneValue.cs new file mode 100644 index 0000000..ee8b3cf --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlAttributeNoneValue.cs @@ -0,0 +1,43 @@ +using System.Text; + +namespace WebExpress.WebHtml +{ + /// + /// Ein Attribut ohne wert + /// z.B. required in input + /// + public class HtmlAttributeNoneValue : IHtmlAttribute + { + /// + /// Returns or sets the name. des Attributes + /// + public string Name { get; set; } + + /// + /// Constructor + /// + public HtmlAttributeNoneValue() + { + + } + + /// + /// Constructor + /// + /// Der Name + public HtmlAttributeNoneValue(string name) + { + Name = name; + } + + /// + /// Convert to a string using a StringBuilder. + /// + /// The string builder. + /// The call depth. + public virtual void ToString(StringBuilder builder, int deep) + { + builder.Append(Name); + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlComment.cs b/src/WebExpress/WebHtml/HtmlComment.cs new file mode 100644 index 0000000..4bbfc7f --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlComment.cs @@ -0,0 +1,41 @@ +using System.Text; + +namespace WebExpress.WebHtml +{ + public class HtmlComment : IHtmlNode + { + /// + /// Returns or sets the text. + /// + public string Text { get; set; } + + /// + /// Constructor + /// + public HtmlComment() + { + + } + + /// + /// Constructor + /// + /// The text. + public HtmlComment(string text) + { + Text = text; + } + + /// + /// Convert to a string using a StringBuilder. + /// + /// The string builder. + /// The call depth. + public virtual void ToString(StringBuilder builder, int deep) + { + builder.Append(""); + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElement.cs b/src/WebExpress/WebHtml/HtmlElement.cs new file mode 100644 index 0000000..5fb350c --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElement.cs @@ -0,0 +1,400 @@ +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace WebExpress.WebHtml +{ + public class HtmlElement : IHtmlNode + { + /// + /// Returns or sets the name. des Attributes + /// + protected string ElementName { get; set; } + + /// + /// Liefert oder setzt die Attribute + /// + protected List Attributes { get; private set; } + + /// + /// Liefert oder setzt die Attribute + /// + protected List Elements { get; private set; } + + /// + /// Returns or sets Returns or sets the id. + /// + public string Id + { + get => GetAttribute("id"); + set => SetAttribute("id", value); + } + + /// + /// Liefert oder setzt die Css-Klasse + /// + public string Class + { + get => GetAttribute("class"); + set => SetAttribute("class", value); + } + + /// + /// Liefert oder setzt die Css-Style + /// + public string Style + { + get => GetAttribute("style"); + set => SetAttribute("style", value); + } + + /// + /// Liefert oder setzt die Rolle + /// + public string Role + { + get => GetAttribute("role"); + set => SetAttribute("role", value); + } + + /// + /// Liefert oder setzt das HTML5 Data-Attribut + /// + public string DataToggle + { + get => GetAttribute("data-toggle"); + set => SetAttribute("data-toggle", value); + } + + /// + /// Liefert oder setzt das HTML5 Data-Attribut + /// + public string DataProvide + { + get => GetAttribute("data-provide"); + set => SetAttribute("data-provide", value); + } + + /// + /// Liefert oder setzt die OnClick-Attribut + /// + public string OnClick + { + get => GetAttribute("onclick"); + set => SetAttribute("onclick", value); + } + + /// + /// Bestimmt, ob das Element inline ist + /// + public bool Inline { get; set; } + + /// + /// Bestimmt ob das Element einen End-Tag benötigt + /// z.B.: true =
false =
+ ///
+ public bool CloseTag { get; protected set; } + + /// + /// Konstruktr + /// + /// Der Name des Elements + public HtmlElement(string name, bool closeTag = true) + { + ElementName = name; + Attributes = new List(); + Elements = new List(); + CloseTag = closeTag; + } + + /// + /// Konstruktr + /// + /// Der Name des Elements + public HtmlElement(string name, bool closeTag, params IHtml[] nodes) + : this(name, closeTag) + { + foreach (var v in nodes) + { + if (v is HtmlAttribute) + { + Attributes.Add(v as HtmlAttribute); + } + else if (v is HtmlElement) + { + Elements.Add(v as HtmlElement); + } + else if (v is HtmlText) + { + Elements.Add(v as HtmlText); + } + } + } + + /// + /// Liefert den Wert eines Attributs + /// + /// Der Attributname + /// Der Wert des Attributes + 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; + } + + /// + /// Prüft ob ein Attribut gesetzt ist + /// + /// Der Attributname + /// true wenn Attribut vorhanden, false sonst + protected bool HasAttribute(string name) + { + var a = Attributes.Where(x => x.Name == name).FirstOrDefault(); + + return (a != null); + } + + /// + /// Setzt den Wert eines Attributs + /// + /// Der Attributname + /// Der Wert des Attributes + 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 + /// + /// Der Attributname + protected void SetAttribute(string name) + { + var a = Attributes.Where(x => x.Name == name).FirstOrDefault(); + + if (a == null) + { + Attributes.Add(new HtmlAttributeNoneValue(name)); + } + } + + /// + /// Entfernt ein Attribut + /// + /// Der Attributname + protected void RemoveAttribute(string name) + { + var a = Attributes.Where(x => x.Name == name).FirstOrDefault(); + + if (a != null) + { + Attributes.Remove(a); + } + } + + /// + /// Liefert ein Element anhand seines Namens + /// + /// Der Elementname + /// Das Element + protected HtmlElement GetElement(string name) + { + var a = Elements.Where(x => x is HtmlElement && (x as HtmlElement).ElementName == name).FirstOrDefault(); + + return a as HtmlElement; + } + + /// + /// Setzt ein Element anhand seines Namens + /// + /// Das 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); + } + } + + /// + /// Liefert den 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 StringBuilder. + /// + /// The string builder. + /// The call depth. + /// Abschlustag auf neuer Zeile beginnen + protected virtual void ToPostString(StringBuilder builder, int deep, bool nl = true) + { + if (!Inline && nl) + { + builder.AppendLine(); + builder.Append(string.Empty.PadRight(deep)); + } + + builder.Append(""); + } + + /// + /// Setzt den Wert eines Attributs + /// + /// Der Attributname + /// Der Wert des Attributes + public void AddUserAttribute(string name, string value) + { + SetAttribute(name, value); + } + + /// + /// Liefert den Wert eines Attributs + /// + /// Der Attributname + /// Der Wert des Attributes + public string GetUserAttribute(string name) + { + return GetAttribute(name); + } + + /// + /// Prüft ob ein Attribut gesetzt ist + /// + /// Der Attributname + /// true wenn Attribut vorhanden, false sonst + public bool HasUserAttribute(string name) + { + return HasAttribute(name); + } + + /// + /// Entfernt ein Attribut + /// + /// Der Attributname + protected void RemoveUserAttribute(string name) + { + RemoveAttribute(name); + } + + /// + /// In String konvertieren + /// + /// Das Objekt als String + public override string ToString() + { + var builder = new StringBuilder(); + ToString(builder, 0); + + return builder.ToString(); + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementEditDel.cs b/src/WebExpress/WebHtml/HtmlElementEditDel.cs new file mode 100644 index 0000000..f069323 --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementEditDel.cs @@ -0,0 +1,78 @@ +using System.Linq; +using System.Text; + +namespace WebExpress.WebHtml +{ + /// + /// Markiert einen aus dem Dokument entfernten Teil. + /// + public class HtmlElementEditDel : HtmlElement, IHtmlElementEdit + { + /// + /// Returns or sets the text. + /// + 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)); } + } + + /// + /// Liefert oder setzt die URI einer Quelle, die die Änderung ausgelöst hat (z.B. eine Ticketnummer in einem Bugtrack-System). + /// + public string Cite + { + get => GetAttribute("cite"); + set => SetAttribute("cite", value); + } + + /// + /// Liefert oder setzt die indiziert das Datum und die Uhrzeit, wann The text. geändert wurde. + /// Wenn der Wert nicht als Datum mit optionaler Zeitangabe erkannt werden kann, hat dieses Element keinen Bezug zur Zeit. + /// + public string DateTime + { + get => GetAttribute("datetime"); + set => SetAttribute("datetime", value); + } + + /// + /// Constructor + /// + public HtmlElementEditDel() + : base("del") + { + + } + + /// + /// Constructor + /// + /// The content of the html element. + public HtmlElementEditDel(string text) + : this() + { + Text = text; + } + + /// + /// Constructor + /// + /// The content of the html element. + public HtmlElementEditDel(params IHtmlNode[] nodes) + : this() + { + Elements.AddRange(nodes); + } + + /// + /// Convert to a string using a StringBuilder. + /// + /// The string builder. + /// The call depth. + public override void ToString(StringBuilder builder, int deep) + { + base.ToString(builder, deep); + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementEditIns.cs b/src/WebExpress/WebHtml/HtmlElementEditIns.cs new file mode 100644 index 0000000..370a405 --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementEditIns.cs @@ -0,0 +1,78 @@ +using System.Linq; +using System.Text; + +namespace WebExpress.WebHtml +{ + /// + /// Markiert einen zum Dokument hinzugefügten Teil. + /// + public class HtmlElementEditIns : HtmlElement, IHtmlElementEdit + { + /// + /// Returns or sets the text. + /// + 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)); } + } + + /// + /// Liefert oder setzt die URI einer Quelle, die die Änderung ausgelöst hat (z.B. eine Ticketnummer in einem Bugtrack-System). + /// + public string Cite + { + get => GetAttribute("cite"); + set => SetAttribute("cite", value); + } + + /// + /// Liefert oder setzt die indiziert das Datum und die Uhrzeit, wann The text. geändert wurde. + /// Wenn der Wert nicht als Datum mit optionaler Zeitangabe erkannt werden kann, hat dieses Element keinen Bezug zur Zeit. + /// + public string DateTime + { + get => GetAttribute("datetime"); + set => SetAttribute("datetime", value); + } + + /// + /// Constructor + /// + public HtmlElementEditIns() + : base("ins") + { + + } + + /// + /// Constructor + /// + /// The content of the html element. + public HtmlElementEditIns(string text) + : this() + { + Text = text; + } + + /// + /// Constructor + /// + /// The content of the html element. + public HtmlElementEditIns(params IHtmlNode[] nodes) + : this() + { + Elements.AddRange(nodes); + } + + /// + /// Convert to a string using a StringBuilder. + /// + /// The string builder. + /// The call depth. + public override void ToString(StringBuilder builder, int deep) + { + base.ToString(builder, deep); + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementEmbeddedEmbed.cs b/src/WebExpress/WebHtml/HtmlElementEmbeddedEmbed.cs new file mode 100644 index 0000000..7dc7cc7 --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementEmbeddedEmbed.cs @@ -0,0 +1,46 @@ +using System.Collections.Generic; + +namespace WebExpress.WebHtml +{ + /// + /// Steht für einen Einbindungspunkt für externe Ressourcen. Dies sind typischerweise keine + /// HTML-Inhalte, sondern beispielsweise eine Applikation oder interaktiver Inhalt, + /// der mit Hilfe eines Plugins (anstatt nativ durch das Benutzerprogramms) dargestellt wird. + /// + public class HtmlElementEmbeddedEmbed : HtmlElement, IHtmlElementEmbedded + { + /// + /// Returns the elements. + /// + public new List Elements => base.Elements; + + /// + /// Constructor + /// + public HtmlElementEmbeddedEmbed() + : base("embed") + { + + } + + /// + /// Constructor + /// + /// 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); + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementEmbeddedIframe.cs b/src/WebExpress/WebHtml/HtmlElementEmbeddedIframe.cs new file mode 100644 index 0000000..f8bfa7c --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementEmbeddedIframe.cs @@ -0,0 +1,44 @@ +using System.Collections.Generic; + +namespace WebExpress.WebHtml +{ + /// + /// Definiert einen Rahmen, mit dem ein HTML-Dokument in seinem eigenen Kontext in das aktuelle Dokument eingebettet werden kann. + /// + public class HtmlElementEmbeddedIframe : HtmlElement, IHtmlElementEmbedded + { + /// + /// Returns the elements. + /// + public new List Elements => base.Elements; + + /// + /// Constructor + /// + public HtmlElementEmbeddedIframe() + : base("iframe") + { + + } + + /// + /// Constructor + /// + /// 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); + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementEmbeddedObject.cs b/src/WebExpress/WebHtml/HtmlElementEmbeddedObject.cs new file mode 100644 index 0000000..0fe5b31 --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementEmbeddedObject.cs @@ -0,0 +1,46 @@ +using System.Collections.Generic; + +namespace WebExpress.WebHtml +{ + /// + /// Steht für allgemeinen externen Inhalt, der je nach Kontext als Bild, + /// "verschachtelter Browsing-Kontext" (s. iframe), oder externer Inhalt + /// (der mit Hilfe eines Plugins darsgestellt wird) betrachtet wird. + /// + public class HtmlElementEmbeddedObject : HtmlElement, IHtmlElementEmbedded + { + /// + /// Returns the elements. + /// + public new List Elements => base.Elements; + + /// + /// Constructor + /// + public HtmlElementEmbeddedObject() + : base("object") + { + + } + + /// + /// Constructor + /// + /// 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); + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementEmbeddedParam.cs b/src/WebExpress/WebHtml/HtmlElementEmbeddedParam.cs new file mode 100644 index 0000000..9335468 --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementEmbeddedParam.cs @@ -0,0 +1,18 @@ +namespace WebExpress.WebHtml +{ + /// + /// Definiert Parameter für ein Plugin, das für die Darstellung eines + /// mit eingebundenen Elements verwendet werden. + /// + public class HtmlElementEmbeddedParam : HtmlElement, IHtmlElementEmbedded + { + /// + /// Constructor + /// + public HtmlElementEmbeddedParam() + : base("param", false) + { + + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementEmbeddedPicture.cs b/src/WebExpress/WebHtml/HtmlElementEmbeddedPicture.cs new file mode 100644 index 0000000..7eac29f --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementEmbeddedPicture.cs @@ -0,0 +1,44 @@ +using System.Collections.Generic; + +namespace WebExpress.WebHtml +{ + /// + /// + /// + public class HtmlElementEmbeddedPicture : HtmlElement, IHtmlElementEmbedded + { + /// + /// Returns the elements. + /// + public new List Elements => base.Elements; + + /// + /// Constructor + /// + public HtmlElementEmbeddedPicture() + : base("picture") + { + + } + + /// + /// Constructor + /// + /// 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); + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementEmbeddedSource.cs b/src/WebExpress/WebHtml/HtmlElementEmbeddedSource.cs new file mode 100644 index 0000000..9fdbef3 --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementEmbeddedSource.cs @@ -0,0 +1,18 @@ +namespace WebExpress.WebHtml +{ + /// + /// Ermöglicht es Autoren, alternative Medienressourcen (z.B. verschiedene Audio- oder Videoformate) + /// für Medienelemente wie + public class HtmlElementEmbeddedSource : HtmlElement, IHtmlElementEmbedded + { + /// + /// Constructor + /// + public HtmlElementEmbeddedSource() + : base("source", false) + { + + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementFieldButton.cs b/src/WebExpress/WebHtml/HtmlElementFieldButton.cs new file mode 100644 index 0000000..96c756c --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementFieldButton.cs @@ -0,0 +1,134 @@ +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace WebExpress.WebHtml +{ + /// + /// Html-Element Button. + /// + public class HtmlElementFieldButton : HtmlElement, IHtmlFormularItem + { + /// + /// Returns or sets the name of the input field. + /// + public string Name + { + get => GetAttribute("name"); + set => SetAttribute("name", value); + } + + /// + /// Returns or sets the text. + /// + 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)); } + } + + /// + /// Returns the elements. + /// + public new List Elements => base.Elements; + + /// + /// Returns or sets a value. + /// + public string Value + { + get => GetAttribute("value"); + set => SetAttribute("value", value); + } + + /// + /// Returns or sets whether the button is disabled. + /// + public bool Disabled + { + get => HasAttribute("disabled"); + set { if (value) { SetAttribute("disabled"); } else { RemoveAttribute("disabled"); } } + } + + /// + /// Returns or sets the identification name of the form element to which it is associated. + /// + public string Form + { + get => GetAttribute("form"); + set => SetAttribute("form", value); + } + + /// + /// Returns or sets the type of button (button, submit, reset). + /// + public string Type + { + get => GetAttribute("type"); + set => SetAttribute("type", value); + } + + /// + /// Returns or sets the tooltip. + /// + public string Title + { + get => GetAttribute("title"); + set => SetAttribute("title", value); + } + + /// + /// Constructor + /// + public HtmlElementFieldButton() + : base("button") + { + + } + + /// + /// Constructor + /// + /// The content of the html element. + public HtmlElementFieldButton(string text) + : this() + { + Text = text; + } + + /// + /// Constructor + /// + /// 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); + } + + /// + /// Convert to a string using a StringBuilder. + /// + /// The string builder. + /// 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/WebHtml/HtmlElementFieldInput.cs b/src/WebExpress/WebHtml/HtmlElementFieldInput.cs new file mode 100644 index 0000000..96d4a3f --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementFieldInput.cs @@ -0,0 +1,211 @@ +using System.Text; + +namespace WebExpress.WebHtml +{ + /// + /// Steht für ein Feld für Benutzereingaben eines bestimmten Typs. Der Typ (Radiobutton, Ankreuzfeld, Texteingabe, etc.) wird anhand des type-Attributs angegeben. + /// + public class HtmlElementFieldInput : HtmlElement, IHtmlFormularItem + { + /// + /// Returns or sets the name of the input field. + /// + public string Name + { + get => GetAttribute("name"); + set => SetAttribute("name", value); + } + + /// + /// Liefert oder setzt den Mindestwert + /// + public string Type + { + get => GetAttribute("type"); + set => SetAttribute("type", value); + } + + /// + /// Returns or sets the value. des Eingabefeldes + /// + public string Value + { + get => GetAttribute("value"); + set => SetAttribute("value", value?.Replace("'", "'")?.Replace("\"", """)); + } + + /// + /// Liefert oder setzt die Zeichenlänge bei text, search, tel, url, email, oder password + /// Falls kein Wert angegeben wird, wird der Standardwert 20 verwendet. + /// + public string Size + { + get => GetAttribute("size"); + set => SetAttribute("size", value); + } + + /// + /// Liefert oder setzt ob das Felf schreibgeschützt ist + /// + public string Readonly + { + get => GetAttribute("readonly"); + set => SetAttribute("readonly", value); + } + + /// + /// Liefert oder setzt ob das Eingabefeld verwendet werden kann + /// + public bool Disabled + { + get => HasAttribute("disabled"); + set { if (value) { SetAttribute("disabled"); } else { RemoveAttribute("disabled"); } } + } + + /// + /// Liefert oder setzt den Mindestwert + /// + public string Min + { + get => GetAttribute("min"); + set => SetAttribute("min", value); + } + + /// + /// Liefert oder setzt den Maximalenwert + /// + public string Max + { + get => GetAttribute("max"); + set => SetAttribute("max", value); + } + + /// + /// Liefert oder setzt die Schrittweite bei numerische, Datums- oder Zeitangaben + /// + public string Step + { + get => GetAttribute("step"); + set => SetAttribute("step", value); + } + + /// + /// Returns or sets the name. einer Liste (datalist) + /// + public string List + { + get => GetAttribute("list"); + set => SetAttribute("list", value); + } + + /// + /// Liefert oder setzt ob mehrfacheingaben von Datei-Uploads und Emaileingaben möglich sind + /// + public string Multiple + { + get => GetAttribute("multiple"); + set => SetAttribute("multiple", value); + } + + /// + /// Returns or sets the minimum length. + /// + public string MinLength + { + get => GetAttribute("minlength"); + set => SetAttribute("minlength", value); + } + + /// + /// Returns or sets the maximum length. + /// + public string MaxLength + { + get => GetAttribute("maxlength"); + set => SetAttribute("maxlength", value); + } + + /// + /// Returns or sets whether inputs are enforced. + /// + public bool Required + { + get => HasAttribute("required"); + set { if (value) { SetAttribute("required"); } else { RemoveAttribute("required"); } } + } + + /// + /// Liefert oder setzt ob eine Auswahl erfolt (nur bei Radio- und Check) + /// + public bool Checked + { + get => HasAttribute("checked"); + set { if (value) { SetAttribute("checked"); } else { RemoveAttribute("checked"); } } + } + + /// + /// Returns or sets a search pattern that checks the content. + /// + public string Pattern + { + get => GetAttribute("pattern"); + set => SetAttribute("pattern", value); + } + + /// + /// Returns or sets a placeholder text. + /// + public string Placeholder + { + get => GetAttribute("placeholder"); + set => SetAttribute("placeholder", value); + } + + /// + /// Liefert oder setzt die Eingabemethode (hilft mobilen Geräten, die richtige Tastatur-(belegung) zu wählen) + /// + public string Inputmode + { + get => GetAttribute("inputmode"); + set => SetAttribute("inputmode", value); + } + + /// + /// Returns or sets the identification name of the form element to which it is associated. + /// + public string Form + { + get => GetAttribute("form"); + set => SetAttribute("form", value); + } + + /// + /// Constructor + /// + public HtmlElementFieldInput() + : base("input") + { + CloseTag = false; + } + + /// + /// Constructor + /// + /// The content of the html element. + public HtmlElementFieldInput(params IHtmlNode[] nodes) + : this() + { + Elements.AddRange(nodes); + } + + /// + /// Convert to a string using a StringBuilder. + /// + /// The string builder. + /// The call depth. + public override void ToString(StringBuilder builder, int deep) + { + base.ToString(builder, deep); + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementFieldLabel.cs b/src/WebExpress/WebHtml/HtmlElementFieldLabel.cs new file mode 100644 index 0000000..c8cf593 --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementFieldLabel.cs @@ -0,0 +1,70 @@ +using System.Linq; +using System.Text; + +namespace WebExpress.WebHtml +{ + /// + /// Kennzeichnet die Beschriftung für ein Formular-Kontrollelement (z.B. Texteingabefelder). + /// + /// + /// + public class HtmlElementFieldLabel : HtmlElement, IHtmlFormularItem + { + /// + /// Returns or sets the name of the input field. + /// + public string For + { + get => GetAttribute("for"); + set => SetAttribute("for", value); + } + + /// + /// Returns or sets the text. + /// + 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)); } + } + + /// + /// Constructor + /// + public HtmlElementFieldLabel() + : base("label") + { + + } + + /// + /// Constructor + /// + /// The content of the html element. + public HtmlElementFieldLabel(string text) + : this() + { + Text = text; + } + + /// + /// Constructor + /// + /// The content of the html element. + public HtmlElementFieldLabel(params IHtmlNode[] nodes) + : this() + { + Elements.AddRange(nodes); + } + + /// + /// Convert to a string using a StringBuilder. + /// + /// The string builder. + /// The call depth. + public override void ToString(StringBuilder builder, int deep) + { + base.ToString(builder, deep); + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementFieldLegend.cs b/src/WebExpress/WebHtml/HtmlElementFieldLegend.cs new file mode 100644 index 0000000..b00e32f --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementFieldLegend.cs @@ -0,0 +1,37 @@ +using System.Linq; + +namespace WebExpress.WebHtml +{ + /// + /// Kennzeichnet eine Beschriftung für ein
-Element. + ///
+ public class HtmlElementFieldLegend : HtmlElement + { + /// + /// Returns or sets the text. + /// + 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)); } + } + + /// + /// Constructor + /// + public HtmlElementFieldLegend() + : base("legend") + { + } + + /// + /// Constructor + /// + /// The content of the html element. + public HtmlElementFieldLegend(string text) + : this() + { + Text = text; + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementFieldSelect.cs b/src/WebExpress/WebHtml/HtmlElementFieldSelect.cs new file mode 100644 index 0000000..3306181 --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementFieldSelect.cs @@ -0,0 +1,94 @@ +using System.Collections.Generic; +using System.Text; + +namespace WebExpress.WebHtml +{ + /// + /// Kennzeichnet ein Kontrollelement, mit dem aus einer Reihe von Optionen ausgewählt werden kann. + /// + /// + public class HtmlElementFieldSelect : HtmlElement, IHtmlFormularItem + { + /// + /// Returns the elements. + /// + public new List Elements => base.Elements; + + /// + /// Returns or sets the name of the input field. + /// + public string Name + { + get => GetAttribute("name"); + set => SetAttribute("name", value); + } + + /// + /// Liefert oder setzt die Anzahl der gleichzeitig sichtbaren Elemente der Auswahlliste + /// + public string Size + { + get => GetAttribute("size"); + set => SetAttribute("size", value); + } + + /// + /// Returns or sets the identification name of the form element to which it is associated. + /// + public string Form + { + get => GetAttribute("form"); + set => SetAttribute("form", value); + } + + /// + /// Liefert oder setzt ob das Eingabefeld verwendet werden kann + /// + public bool Disabled + { + get => HasAttribute("disabled"); + set { if (value) { SetAttribute("disabled"); } else { RemoveAttribute("disabled"); } } + } + + /// + /// Returns or sets the OnChange attribute. + /// + public string OnChange + { + get => GetAttribute("onchange"); + set => SetAttribute("onchange", value); + } + + /// + /// Constructor + /// + public HtmlElementFieldSelect() + : base("select") + { + + } + + /// + /// Constructor + /// + /// The content of the html element. + public HtmlElementFieldSelect(params IHtmlNode[] nodes) + : this() + { + Elements.AddRange(nodes); + } + + /// + /// Convert to a string using a StringBuilder. + /// + /// The string builder. + /// The call depth. + public override void ToString(StringBuilder builder, int deep) + { + base.ToString(builder, deep); + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementFormDatalist.cs b/src/WebExpress/WebHtml/HtmlElementFormDatalist.cs new file mode 100644 index 0000000..058e757 --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementFormDatalist.cs @@ -0,0 +1,54 @@ +using System.Collections.Generic; +using System.Text; + +namespace WebExpress.WebHtml +{ + /// + /// Steht für eine Sammlung vordefinierter Optionen für andere Kontrollelemente. + /// + public class HtmlElementFormDatalist : HtmlElement, IHtmlElementForm + { + /// + /// Returns the elements. + /// + public new List Elements => base.Elements; + + /// + /// Constructor + /// + public HtmlElementFormDatalist() + : base("datalist") + { + + } + + /// + /// Constructor + /// + /// The content of the html element. + public HtmlElementFormDatalist(string text) + : this() + { + } + + /// + /// Constructor + /// + /// The content of the html element. + public HtmlElementFormDatalist(params IHtmlNode[] nodes) + : this() + { + Elements.AddRange(nodes); + } + + /// + /// Convert to a string using a StringBuilder. + /// + /// The string builder. + /// The call depth. + public override void ToString(StringBuilder builder, int deep) + { + base.ToString(builder, deep); + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementFormFieldset.cs b/src/WebExpress/WebHtml/HtmlElementFormFieldset.cs new file mode 100644 index 0000000..8c2d024 --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementFormFieldset.cs @@ -0,0 +1,73 @@ +using System.Collections.Generic; +using System.Text; + +namespace WebExpress.WebHtml +{ + /// + /// Steht für eine Gruppe von Kontrollelementen. + /// + public class HtmlElementFormFieldset : HtmlElement, IHtmlFormularItem + { + /// + /// Returns or sets the name of the input field. + /// + public string Name + { + get => GetAttribute("name"); + set => SetAttribute("name", value); + } + + + /// + /// Liefert oder setzt die Label-Eigenschaft + /// + public bool Disable + { + get => HasAttribute("disabled"); + set { if (value) { SetAttribute("disabled"); } else { RemoveAttribute("disabled"); } } + } + + /// + /// Returns or sets the identification name of the form element to which it is associated. + /// + public string Form + { + get => GetAttribute("form"); + set => SetAttribute("form", value); + } + + /// + /// Returns the elements. + /// + public new List Elements => base.Elements; + + /// + /// Constructor + /// + public HtmlElementFormFieldset() + : base("fieldset") + { + CloseTag = false; + } + + /// + /// Constructor + /// + /// The content of the html element. + public HtmlElementFormFieldset(params IHtmlNode[] nodes) + : this() + { + Elements.AddRange(nodes); + } + + /// + /// Convert to a string using a StringBuilder. + /// + /// The string builder. + /// The call depth. + public override void ToString(StringBuilder builder, int deep) + { + base.ToString(builder, deep); + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementFormForm.cs b/src/WebExpress/WebHtml/HtmlElementFormForm.cs new file mode 100644 index 0000000..f637d5c --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementFormForm.cs @@ -0,0 +1,109 @@ +using System.Collections.Generic; +using System.Text; + +namespace WebExpress.WebHtml +{ + /// + /// Markiert einFormular. Formulare bestehen typischerweise aus einer Reihe + /// von Kontrollelementen, deren Werte zur weiteren Verarbeitung an einen + /// Server übertragen werden. + /// + public class HtmlElementFormForm : HtmlElement, IHtmlElementForm + { + /// + /// Returns or sets the name of the form. + /// + public string Name + { + get => GetAttribute("name"); + set => SetAttribute("name", value); + } + + /// + /// Liefert oder setzt die Zeichenkodierung + /// + public string AcceptCharset + { + get => GetAttribute("accept-charset"); + set => SetAttribute("accept-charset", value); + } + + /// + /// Liefert oder setzt die Zeichenkodierung + /// + public TypeEnctype Enctype + { + get => TypeEnctypeExtensions.Convert(GetAttribute("enctype")); + set => SetAttribute("enctype", value.Convert()); + } + /// + /// Liefert oder setzt den Methode Post oder get + /// + public string Method + { + get => GetAttribute("method"); + set => SetAttribute("method", value); + } + + /// + /// Liefert oder setzt die URL + /// + public string Action + { + get => GetAttribute("action"); + set => SetAttribute("action", value); + } + + /// + /// Liefert oder setzt das Zielfenster + /// + public string Target + { + get => GetAttribute("target"); + set => SetAttribute("target", value); + } + + /// + /// Returns the elements. + /// + public new List Elements => base.Elements; + + /// + /// Constructor + /// + public HtmlElementFormForm() + : base("form") + { + + } + + /// + /// Constructor + /// + /// The content of the html element. + public HtmlElementFormForm(string text) + : this() + { + } + + /// + /// Constructor + /// + /// The content of the html element. + public HtmlElementFormForm(params IHtmlNode[] nodes) + : this() + { + Elements.AddRange(nodes); + } + + /// + /// Convert to a string using a StringBuilder. + /// + /// The string builder. + /// The call depth. + public override void ToString(StringBuilder builder, int deep) + { + base.ToString(builder, deep); + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementFormKeygen.cs b/src/WebExpress/WebHtml/HtmlElementFormKeygen.cs new file mode 100644 index 0000000..80fad74 --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementFormKeygen.cs @@ -0,0 +1,39 @@ +using System.Text; + +namespace WebExpress.WebHtml +{ + /// + /// Steht für ein Kontrollelement zur Erzeugung einesPaares aus öffentlichem und privaten Schlüssel und zum Versenden des öffentlichen Schlüssels. + /// + public class HtmlElementFormKeygen : HtmlElement, IHtmlFormularItem + { + /// + /// Constructor + /// + public HtmlElementFormKeygen() + : base("keygen") + { + + } + + /// + /// Constructor + /// + /// The content of the html element. + public HtmlElementFormKeygen(params IHtmlNode[] nodes) + : this() + { + Elements.AddRange(nodes); + } + + /// + /// Convert to a string using a StringBuilder. + /// + /// The string builder. + /// The call depth. + public override void ToString(StringBuilder builder, int deep) + { + base.ToString(builder, deep); + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementFormMeter.cs b/src/WebExpress/WebHtml/HtmlElementFormMeter.cs new file mode 100644 index 0000000..564ebef --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementFormMeter.cs @@ -0,0 +1,102 @@ +using System.Linq; +using System.Text; + +namespace WebExpress.WebHtml +{ + /// + /// Steht für eine Messskala (oder deren Teilwerte) innerhalb eines bekannten Bereichs. + /// + public class HtmlElementFormMeter : HtmlElement, IHtmlElementForm + { + /// + /// Returns or sets the text. + /// + 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)); } + } + + /// + /// Returns or sets the value. + /// + public string Value + { + get => GetAttribute("value"); + set => SetAttribute("value", value); + } + + /// + /// Liefert oder setzt die untere Grenze der Skala + /// + public string Min + { + get => GetAttribute("min"); + set => SetAttribute("min", value); + } + + /// + /// Liefert oder setzt die obere Grenze der Skala + /// + public string Max + { + get => GetAttribute("max"); + set => SetAttribute("max", value); + } + + /// + /// Liefert oder setzt die obere Grenze des "Niedrig"-Bereichs der Skala + /// + public string Low + { + get => GetAttribute("low"); + set => SetAttribute("low", value); + } + + /// + /// Liefert oder setzt die untere Grenze des "Hoch"-Bereichs der Skala + /// + public string High + { + get => GetAttribute("high"); + set => SetAttribute("high", value); + } + + /// + /// Liefert oder setzt den optimaler Wert der Skala + /// + public string Optimum + { + get => GetAttribute("optimum"); + set => SetAttribute("optimum", value); + } + + /// + /// Constructor + /// + public HtmlElementFormMeter() + : base("meter") + { + } + + /// + /// Constructor + /// + /// The content of the html element. + public HtmlElementFormMeter(string text) + : this() + { + Text = text; + } + + /// + /// Convert to a string using a StringBuilder. + /// + /// The string builder. + /// The call depth. + public override void ToString(StringBuilder builder, int deep) + { + base.ToString(builder, deep); + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementFormOptgroup.cs b/src/WebExpress/WebHtml/HtmlElementFormOptgroup.cs new file mode 100644 index 0000000..2da7647 --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementFormOptgroup.cs @@ -0,0 +1,60 @@ +using System.Collections.Generic; +using System.Text; + +namespace WebExpress.WebHtml +{ + /// + /// Steht für eine Reihe logisch gruppierter Auswahloptionen. + /// + /// + public class HtmlElementFormOptgroup : HtmlElement, IHtmlFormularItem + { + /// + /// Returns the elements. + /// + public new List Elements => base.Elements; + + /// + /// Returns or sets the label. + /// + public string Label + { + get => GetAttribute("label"); + set => SetAttribute("label", value); + } + + /// + /// Constructor + /// + public HtmlElementFormOptgroup() + : base("optgroup") + { + + } + + /// + /// Constructor + /// + /// The content of the html element. + public HtmlElementFormOptgroup(params IHtmlNode[] nodes) + : this() + { + Elements.AddRange(nodes); + } + + /// + /// Convert to a string using a StringBuilder. + /// + /// The string builder. + /// The call depth. + public override void ToString(StringBuilder builder, int deep) + { + base.ToString(builder, deep); + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementFormOption.cs b/src/WebExpress/WebHtml/HtmlElementFormOption.cs new file mode 100644 index 0000000..7484cc9 --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementFormOption.cs @@ -0,0 +1,71 @@ +using System.Linq; +using System.Text; + +namespace WebExpress.WebHtml +{ + /// + /// Steht für eine Auswahloption innerhalb eines + /// + /// + /// + /// + public class HtmlElementFormOption : HtmlElement, IHtmlFormularItem + { + /// + /// Returns or sets the text. + /// + 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)); } + } + + /// + /// Returns or sets a value. + /// + public string Value + { + get => GetAttribute("value"); + set => SetAttribute("value", value); + } + + /// + /// Liefert oder setzt ob das Felf ausgewählt ist + /// + public bool Selected + { + get => HasAttribute("selected"); + set { if (value) { SetAttribute("selected"); } else { RemoveAttribute("selected"); } } + } + + /// + /// Constructor + /// + public HtmlElementFormOption() + : base("option") + { + + } + + /// + /// Constructor + /// + /// The content of the html element. + public HtmlElementFormOption(params IHtmlNode[] nodes) + : this() + { + Elements.AddRange(nodes); + } + + /// + /// Convert to a string using a StringBuilder. + /// + /// The string builder. + /// The call depth. + public override void ToString(StringBuilder builder, int deep) + { + base.ToString(builder, deep); + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementFormOutput.cs b/src/WebExpress/WebHtml/HtmlElementFormOutput.cs new file mode 100644 index 0000000..917c00b --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementFormOutput.cs @@ -0,0 +1,39 @@ +using System.Text; + +namespace WebExpress.WebHtml +{ + /// + /// Markiert das Ergebnis einer Berechnung. + /// + public class HtmlElementFormOutput : HtmlElement, IHtmlFormularItem + { + /// + /// Constructor + /// + public HtmlElementFormOutput() + : base("output") + { + + } + + /// + /// Constructor + /// + /// The content of the html element. + public HtmlElementFormOutput(params IHtmlNode[] nodes) + : this() + { + Elements.AddRange(nodes); + } + + /// + /// Convert to a string using a StringBuilder. + /// + /// The string builder. + /// The call depth. + public override void ToString(StringBuilder builder, int deep) + { + base.ToString(builder, deep); + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementFormProgress.cs b/src/WebExpress/WebHtml/HtmlElementFormProgress.cs new file mode 100644 index 0000000..fcb2a89 --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementFormProgress.cs @@ -0,0 +1,75 @@ +using System.Linq; +using System.Text; + +namespace WebExpress.WebHtml +{ + /// + /// Ein Element zurFortschrittsanzeige einer bestimmten Aufgabe. + /// + public class HtmlElementFormProgress : HtmlElement, IHtmlElementForm + { + /// + /// Returns or sets the text. + /// + 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)); } + } + + /// + /// Returns or sets the value. + /// + public string Value + { + get => GetAttribute("value"); + set => SetAttribute("value", value); + } + + /// + /// Liefert oder setzt die untere Grenze der Skala + /// + public string Min + { + get => GetAttribute("min"); + set => SetAttribute("min", value); + } + + /// + /// Liefert oder setzt die obere Grenze der Skala + /// + public string Max + { + get => GetAttribute("max"); + set => SetAttribute("max", value); + } + + /// + /// Constructor + /// + public HtmlElementFormProgress() + : base("progress") + { + } + + /// + /// Constructor + /// + /// The content of the html element. + public HtmlElementFormProgress(string text) + : this() + { + Text = text; + } + + /// + /// Convert to a string using a StringBuilder. + /// + /// The string builder. + /// The call depth. + public override void ToString(StringBuilder builder, int deep) + { + base.ToString(builder, deep); + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementFormTextarea.cs b/src/WebExpress/WebHtml/HtmlElementFormTextarea.cs new file mode 100644 index 0000000..7f1f5d7 --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementFormTextarea.cs @@ -0,0 +1,149 @@ +using System.Linq; +using System.Text; + +namespace WebExpress.WebHtml +{ + /// + /// Markiert ein Element für mehrzeilige Texteingaben. + /// + public class HtmlElementFormTextarea : HtmlElement, IHtmlFormularItem + { + /// + /// Returns or sets the name of the input field. + /// + public string Name + { + get => GetAttribute("name"); + set => SetAttribute("name", value); + } + + /// + /// Returns or sets the value. des Eingabefeldes + /// + 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)); } + } + + /// + /// Liefert oder setzt die Anzahl der anzegeigten Zeilen + /// + public string Rows + { + get => GetAttribute("rows"); + set => SetAttribute("rows", value); + } + + /// + /// Liefert oder setzt die Anzahl der anzegeigten Spalten + /// + public string Cols + { + get => GetAttribute("cols"); + set => SetAttribute("cols", value); + } + + /// + /// Liefert oder setzt ob The text. umgebrochen werden soll + /// Mögliche Werte sind: hard, soft + /// + public string Wrap + { + get => GetAttribute("wrap"); + set => SetAttribute("wrap", value); + } + + /// + /// Liefert oder setzt ob das Felf schreibgeschützt ist + /// + public string Readonly + { + get => GetAttribute("readonly"); + set => SetAttribute("readonly", value); + } + + /// + /// Returns or sets the minimum length. + /// + public string MinLength + { + get => GetAttribute("minlength"); + set => SetAttribute("minlength", value); + } + + /// + /// Returns or sets the maximum length. + /// + public string MaxLength + { + get => GetAttribute("maxlength"); + set => SetAttribute("maxlength", value); + } + + /// + /// Returns or sets whether inputs are enforced. + /// + public bool Required + { + get => HasAttribute("required"); + set { if (value) { SetAttribute("required"); } else { RemoveAttribute("required"); } } + } + + /// + /// Returns or sets a placeholder text. + /// + public string Placeholder + { + get => GetAttribute("placeholder"); + set => SetAttribute("placeholder", value); + } + + /// + /// Returns or sets a search pattern that checks the content. + /// + public string Pattern + { + get => GetAttribute("pattern"); + set => SetAttribute("pattern", value); + } + + /// + /// Returns or sets the identification name of the form element to which it is associated. + /// + public string Form + { + get => GetAttribute("form"); + set => SetAttribute("form", value); + } + + /// + /// Constructor + /// + public HtmlElementFormTextarea() + : base("textarea") + { + + } + + /// + /// Constructor + /// + /// The content of the html element. + public HtmlElementFormTextarea(params IHtmlNode[] nodes) + : this() + { + Elements.AddRange(nodes); + } + + /// + /// Convert to a string using a StringBuilder. + /// + /// The string builder. + /// The call depth. + public override void ToString(StringBuilder builder, int deep) + { + base.ToString(builder, deep); + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementInteractiveCommand.cs b/src/WebExpress/WebHtml/HtmlElementInteractiveCommand.cs new file mode 100644 index 0000000..5951cf2 --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementInteractiveCommand.cs @@ -0,0 +1,44 @@ +using System.Collections.Generic; + +namespace WebExpress.WebHtml +{ + /// + /// Kennzeichnet einenBefehl, welcher vom Benutzer aufgerufen werden kann. + /// + public class HtmlElementInteractiveCommand : HtmlElement, IHtmlElementInteractive + { + /// + /// Returns the elements. + /// + public new List Elements => base.Elements; + + /// + /// Constructor + /// + public HtmlElementInteractiveCommand() + : base("command") + { + + } + + /// + /// Constructor + /// + /// 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); + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementInteractiveDetails.cs b/src/WebExpress/WebHtml/HtmlElementInteractiveDetails.cs new file mode 100644 index 0000000..f7c2bac --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementInteractiveDetails.cs @@ -0,0 +1,44 @@ +using System.Collections.Generic; + +namespace WebExpress.WebHtml +{ + /// + /// Markiert ein Kontrollelement, mit dem der Benutzerzusätzliche Informationen oder Kontrolle erhalten kann. + /// + public class HtmlElementInteractiveDetails : HtmlElement, IHtmlElementInteractive + { + /// + /// Returns the elements. + /// + public new List Elements => base.Elements; + + /// + /// Constructor + /// + public HtmlElementInteractiveDetails() + : base("details") + { + + } + + /// + /// Constructor + /// + /// 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); + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementInteractiveMenu.cs b/src/WebExpress/WebHtml/HtmlElementInteractiveMenu.cs new file mode 100644 index 0000000..e0eb856 --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementInteractiveMenu.cs @@ -0,0 +1,44 @@ +using System.Collections.Generic; + +namespace WebExpress.WebHtml +{ + /// + /// Markiert eineListe mit Befehlen. + /// + public class HtmlElementInteractiveMenu : HtmlElement, IHtmlElementInteractive + { + /// + /// Returns the elements. + /// + public new List Elements => base.Elements; + + /// + /// Constructor + /// + public HtmlElementInteractiveMenu() + : base("menu") + { + + } + + /// + /// Constructor + /// + /// 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); + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementInteractiveSummary.cs b/src/WebExpress/WebHtml/HtmlElementInteractiveSummary.cs new file mode 100644 index 0000000..b51dbc7 --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementInteractiveSummary.cs @@ -0,0 +1,44 @@ +using System.Collections.Generic; + +namespace WebExpress.WebHtml +{ + /// + /// Kennzeichnet eineZusammenfassung oder eineLegende für ein bestimmte
-Element. + ///
+ public class HtmlElementInteractiveSummary : HtmlElement, IHtmlElementInteractive + { + /// + /// Returns the elements. + /// + public new List Elements => base.Elements; + + /// + /// Constructor + /// + public HtmlElementInteractiveSummary() + : base("summary") + { + + } + + /// + /// Constructor + /// + /// 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); + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementMetadataBase.cs b/src/WebExpress/WebHtml/HtmlElementMetadataBase.cs new file mode 100644 index 0000000..b477c36 --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementMetadataBase.cs @@ -0,0 +1,46 @@ +namespace WebExpress.WebHtml +{ + /// + /// Stellt die Basis für relative Verweise da. + /// + public class HtmlElementMetadataBase : HtmlElement, IHtmlElementMetadata + { + /// + /// Liefert oder setzt die Ziel-Url + /// + public string Href + { + get => GetAttribute("href"); + set + { + var url = value; + + if (!string.IsNullOrWhiteSpace(url) && !url.EndsWith("/")) + { + url += url + "/"; + } + + SetAttribute("href", url); + } + } + + /// + /// Constructor + /// + public HtmlElementMetadataBase() + : base("base") + { + CloseTag = false; + } + + /// + /// Constructor + /// + /// The uri. + public HtmlElementMetadataBase(string url) + : this() + { + Href = url; + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementMetadataHead.cs b/src/WebExpress/WebHtml/HtmlElementMetadataHead.cs new file mode 100644 index 0000000..d617974 --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementMetadataHead.cs @@ -0,0 +1,216 @@ +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace WebExpress.WebHtml +{ + /// + /// Bezeichnet eine Sammlung von Metadaten des Dokuments. Hierzu gehören auch Links zu oder Definitionen von Skripts und Stylesheets. + /// + public class HtmlElementMetadataHead : HtmlElement, IHtmlElementMetadata + { + /// + /// Returns or sets the title. + /// + public string Title + { + get => ElementTitle.Title; + set => ElementTitle.Title = value; + } + + /// + /// Liefert oder setzt das TitelElement + /// + private HtmlElementMetadataTitle ElementTitle { get; set; } + + /// + /// Returns or sets the title. + /// + public string Base + { + get => ElementBase.Href; + set => ElementBase.Href = value; + } + + /// + /// Liefert oder setzt das TitelElement + /// + private HtmlElementMetadataBase ElementBase { get; set; } + + /// + /// Liefert oder setzt das Favicon + /// + public IEnumerable Favicons + { + get => (from x in ElementFavicons select new Favicon(x.Href, x.Type)).ToList(); + set + { + ElementFavicons.Clear(); + ElementFavicons.AddRange + ( + from x in value + select new HtmlElementMetadataLink() + { + Href = x.Url, + Rel = "icon", + Type = x.Mediatype != TypeFavicon.Default ? x.GetMediatyp() : "" + }); + } + } + + /// + /// Liefert oder setzt den Favicon-Link + /// + private List ElementFavicons { get; set; } + + /// + /// Liefert oder setzt das internes 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)); } + } + + /// + /// Liefert oder setzt die Style-Elemente + /// + private List ElementStyles { get; set; } + + /// + /// Liefert oder setzt die Scripte + /// + 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)); } + } + + /// + /// Liefert oder setzt die Script-Elemente + /// + private List ElementScripts { get; set; } + + /// + /// Liefert oder setzt das text/javascript + /// + public IEnumerable ScriptLinks + { + get => (from x in ElementScriptLinks select x.Src).ToList(); + set + { + ElementScriptLinks.Clear(); ElementScriptLinks.AddRange(from x in value + select new HtmlElementScriptingScript() { Language = "javascript", Src = x, Type = "text/javascript" }); + } + } + + /// + /// Liefert oder setzt die externen Scripts + /// + private List ElementScriptLinks { get; set; } + + /// + /// Liefert oder setzt das internes Stylesheet + /// + public IEnumerable CssLinks + { + get => (from x in ElementCssLinks select x.Href).ToList(); + set + { + ElementCssLinks.Clear(); ElementCssLinks.AddRange(from x in value + select new HtmlElementMetadataLink() { Rel = "stylesheet", Href = x, Type = "text/css" }); + } + } + + /// + /// Liefert oder setzt den Css-Link + /// + private List ElementCssLinks { get; set; } + + /// + /// Liefert oder setzt die Metadaten + /// + 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)); + } + } + + /// + /// Liefert oder setzt die Metadaten-Elemente + /// + private List ElementMeta { get; set; } + + /// + /// Constructor + /// + 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(); + } + + /// + /// Convert to a string using a StringBuilder. + /// + /// The string builder. + /// The call depth. + public override void ToString(StringBuilder builder, int deep) + { + ToPreString(builder, deep); + + if (!string.IsNullOrWhiteSpace(Title)) + { + ElementTitle.ToString(builder, deep + 1); + } + + if (!string.IsNullOrWhiteSpace(Base)) + { + //ElementBase.ToString(builder, deep + 1); + } + + foreach (var v in ElementFavicons) + { + v.ToString(builder, deep + 1); + } + + foreach (var v in ElementStyles) + { + v.ToString(builder, deep + 1); + } + + foreach (var v in ElementScriptLinks) + { + v.ToString(builder, deep + 1); + } + + foreach (var v in ElementScripts) + { + v.ToString(builder, deep + 1); + } + + foreach (var v in ElementCssLinks) + { + v.ToString(builder, deep + 1); + } + + foreach (var v in ElementMeta) + { + v.ToString(builder, deep + 1); + } + + ToPostString(builder, deep, true); + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementMetadataLink.cs b/src/WebExpress/WebHtml/HtmlElementMetadataLink.cs new file mode 100644 index 0000000..005e954 --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementMetadataLink.cs @@ -0,0 +1,43 @@ +namespace WebExpress.WebHtml +{ + /// + /// Wird verwendet, um externe JavaScript- und CSS-Dateien in das aktuelle HTML-Dokument einzubinden. + /// + public class HtmlElementMetadataLink : HtmlElement, IHtmlElementMetadata + { + /// + /// Liefert oder setzt die Url + /// + public string Href + { + get => GetAttribute("href"); + set => SetAttribute("href", value); + } + + /// + /// Returns or sets the type. + /// + public string Rel + { + get => GetAttribute("rel"); + set => SetAttribute("rel", value); + } + + /// + /// Returns or sets the type. + /// + public string Type + { + get => GetAttribute("type"); + set => SetAttribute("type", value); + } + + /// + /// Constructor + /// + public HtmlElementMetadataLink() + : base("link", false) + { + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementMetadataMeta.cs b/src/WebExpress/WebHtml/HtmlElementMetadataMeta.cs new file mode 100644 index 0000000..1e9045e --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementMetadataMeta.cs @@ -0,0 +1,70 @@ +using System.Text; + +namespace WebExpress.WebHtml +{ + /// + /// Wird für die Definition von Metadaten verwenden, die mit keinem anderen HTML-Element definiert werden können. + /// + public class HtmlElementMetadataMeta : HtmlElement, IHtmlElementMetadata + { + /// + /// Liefert oder setzt den Attributnamen + /// + public string Key { get; set; } + + /// + /// Returns or sets the value. + /// + public string Value + { + get => GetAttribute(Key); + set => SetAttribute(Key, value); + } + + /// + /// Constructor + /// + public HtmlElementMetadataMeta() + : base("meta") + { + } + + /// + /// Constructor + /// + public HtmlElementMetadataMeta(string key) + : this() + { + Key = key; + SetAttribute(Key, ""); + } + + /// + /// Constructor + /// + public HtmlElementMetadataMeta(string key, string value) + : this() + { + Key = key; + SetAttribute(Key, value); + } + + /// + /// Convert to a string using a StringBuilder. + /// + /// The string builder. + /// The call depth. + public override void ToString(StringBuilder builder, int deep) + { + builder.AppendLine(); + builder.Append(string.Empty.PadRight(deep)); + builder.Append("<"); + builder.Append(ElementName); + builder.Append(" "); + builder.Append(Key); + builder.Append("='"); + builder.Append(Value); + builder.Append("'>"); + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementMetadataStyle.cs b/src/WebExpress/WebHtml/HtmlElementMetadataStyle.cs new file mode 100644 index 0000000..3e64576 --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementMetadataStyle.cs @@ -0,0 +1,59 @@ +using System.Text; + +namespace WebExpress.WebHtml +{ + /// + /// Definition eines internen CSS-Stylesheets. + /// + public class HtmlElementMetadataStyle : HtmlElement, IHtmlElementMetadata + { + /// + /// Returns or sets the text. + /// + public string Code { get; set; } + + /// + /// Constructor + /// + public HtmlElementMetadataStyle() + : base("style") + { + + } + + /// + /// Constructor + /// + /// The text. + public HtmlElementMetadataStyle(string code) + : this() + { + Code = code; + } + + /// + /// Convert to a string using a StringBuilder. + /// + /// The string builder. + /// The call depth. + public override void ToString(StringBuilder builder, int deep) + { + builder.Append(string.Empty.PadRight(deep)); + builder.Append("<"); + builder.Append(ElementName); + builder.Append(">"); + + if (!string.IsNullOrWhiteSpace(Code)) + { + builder.Append("\n"); + builder.Append(Code); + builder.Append("\n"); + } + builder.Append(string.Empty.PadRight(deep)); + builder.Append(""); + builder.Append("\n"); + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementMetadataTitle.cs b/src/WebExpress/WebHtml/HtmlElementMetadataTitle.cs new file mode 100644 index 0000000..2b6ce3f --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementMetadataTitle.cs @@ -0,0 +1,55 @@ +using System.Text; + +namespace WebExpress.WebHtml +{ + /// + /// Defines the title of a document that appears in the browser's title bar in the + /// tab of that page. May contain text only. Any tags contained are not interpreted. + /// + public class HtmlElementMetadataTitle : HtmlElement, IHtmlElementMetadata + { + /// + /// The title. + /// + public string Title { get; set; } + + /// + /// Constructor + /// + public HtmlElementMetadataTitle() + : base("title") + { + + } + + /// + /// Constructor + /// + /// The title. + public HtmlElementMetadataTitle(string title) + : this() + { + Title = title; + } + + /// + /// Convert to a string using a StringBuilder. + /// + /// The string builder. + /// The call depth. + public override void ToString(StringBuilder builder, int deep) + { + builder.AppendLine(); + builder.Append(string.Empty.PadRight(deep)); + builder.Append("<"); + builder.Append(ElementName); + builder.Append(">"); + + builder.Append(Title); + + builder.Append(""); + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementMultimediaArea.cs b/src/WebExpress/WebHtml/HtmlElementMultimediaArea.cs new file mode 100644 index 0000000..cffdb0b --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementMultimediaArea.cs @@ -0,0 +1,17 @@ +namespace WebExpress.WebHtml +{ + /// + /// Definiert in Verbindung mit dem -Element eine Image Map. + /// + public class HtmlElementMultimediaArea : HtmlElement, IHtmlElementMultimedia + { + /// + /// Constructor + /// + public HtmlElementMultimediaArea() + : base("area", false) + { + + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementMultimediaAudio.cs b/src/WebExpress/WebHtml/HtmlElementMultimediaAudio.cs new file mode 100644 index 0000000..2291d34 --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementMultimediaAudio.cs @@ -0,0 +1,26 @@ +namespace WebExpress.WebHtml +{ + /// + /// Markiert eine Tondatei oder einen Audiostream. + /// + public class HtmlElementMultimediaAudio : HtmlElement, IHtmlElementMultimedia + { + /// + /// Liefert oder setzt die Audio-Url + /// + public string Src + { + get => GetAttribute("src"); + set => SetAttribute("src", value); + } + + /// + /// Constructor + /// + public HtmlElementMultimediaAudio() + : base("audio", false) + { + + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementMultimediaImg.cs b/src/WebExpress/WebHtml/HtmlElementMultimediaImg.cs new file mode 100644 index 0000000..4e02770 --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementMultimediaImg.cs @@ -0,0 +1,73 @@ +using System; + +namespace WebExpress.WebHtml +{ + /// + /// Steht für ein Bild. + /// + public class HtmlElementMultimediaImg : HtmlElement, IHtmlElementMultimedia + { + /// + /// Liefert oder setzt den alternativen Text, wenn das Bild nicht angezeigt werden kann + /// + public string Alt + { + get => GetAttribute("alt"); + set => SetAttribute("alt", value); + } + + /// + /// Returns or sets the tooltip. + /// + public string Title + { + get => GetAttribute("title"); + set => SetAttribute("title", value); + } + + /// + /// Liefert oder setzt die Bild-Url + /// + public string Src + { + get => GetAttribute("src"); + set => SetAttribute("src", value); + } + + /// + /// Returns or sets the width. + /// + public int Width + { + get => Convert.ToInt32(GetAttribute("width")); + set => SetAttribute("width", value.ToString()); + } + + /// + /// Returns or sets the width. + /// + public int Height + { + get => Convert.ToInt32(GetAttribute("height")); + set => SetAttribute("height", value.ToString()); + } + + /// + /// Liefert oder setzt das Ziel + /// + public string Target + { + get => GetAttribute("target"); + set => SetAttribute("target", value); + } + + /// + /// Constructor + /// + public HtmlElementMultimediaImg() + : base("img", false) + { + + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementMultimediaMap.cs b/src/WebExpress/WebHtml/HtmlElementMultimediaMap.cs new file mode 100644 index 0000000..ea85d91 --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementMultimediaMap.cs @@ -0,0 +1,17 @@ +namespace WebExpress.WebHtml +{ + /// + /// Definiert in Verbindung mit dem -Element eine Image Map. + /// + public class HtmlElementMultimediaMap : HtmlElement, IHtmlElementMultimedia + { + /// + /// Constructor + /// + public HtmlElementMultimediaMap() + : base("map", false) + { + + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementMultimediaMath.cs b/src/WebExpress/WebHtml/HtmlElementMultimediaMath.cs new file mode 100644 index 0000000..5784196 --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementMultimediaMath.cs @@ -0,0 +1,44 @@ +using System.Collections.Generic; + +namespace WebExpress.WebHtml +{ + /// + /// Markiert eine mathematische Formel. + /// + public class HtmlElementMultimediaMath : HtmlElement, IHtmlElementMultimedia + { + /// + /// Returns the elements. + /// + public new List Elements => base.Elements; + + /// + /// Constructor + /// + public HtmlElementMultimediaMath() + : base("math") + { + + } + + /// + /// Constructor + /// + /// 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); + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementMultimediaSvg.cs b/src/WebExpress/WebHtml/HtmlElementMultimediaSvg.cs new file mode 100644 index 0000000..67ef171 --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementMultimediaSvg.cs @@ -0,0 +1,72 @@ +using System; +using System.Collections.Generic; + +namespace WebExpress.WebHtml +{ + /// + /// Definiert eine eingebettete Vektorgrafik. + /// + public class HtmlElementMultimediaSvg : HtmlElement, IHtmlElementMultimedia + { + /// + /// Returns the elements. + /// + public new List Elements => base.Elements; + + /// + /// Returns or sets the width. + /// + public int Width + { + get => Convert.ToInt32(GetAttribute("width")); + set => SetAttribute("width", value.ToString()); + } + + /// + /// Returns or sets the width. + /// + public int Height + { + get => Convert.ToInt32(GetAttribute("height")); + set => SetAttribute("height", value.ToString()); + } + + /// + /// Liefert oder setzt das Ziel + /// + public string Target + { + get => GetAttribute("target"); + set => SetAttribute("target", value); + } + + /// + /// Constructor + /// + public HtmlElementMultimediaSvg() + : base("svg") + { + + } + + /// + /// Constructor + /// + /// 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); + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementMultimediaTrack.cs b/src/WebExpress/WebHtml/HtmlElementMultimediaTrack.cs new file mode 100644 index 0000000..5cdd82c --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementMultimediaTrack.cs @@ -0,0 +1,17 @@ +namespace WebExpress.WebHtml +{ + /// + /// Hiermit können zusätzliche Medienspuren (z.B. Untertitel) für Elemente wie + public class HtmlElementMultimediaTrack : HtmlElement, IHtmlElementMultimedia + { + /// + /// Constructor + /// + public HtmlElementMultimediaTrack() + : base("track", false) + { + + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementMultimediaVideo.cs b/src/WebExpress/WebHtml/HtmlElementMultimediaVideo.cs new file mode 100644 index 0000000..774d7f5 --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementMultimediaVideo.cs @@ -0,0 +1,26 @@ +namespace WebExpress.WebHtml +{ + /// + /// Steht für eine Videodatei und die dazugehörigen Audiodateien, sowie die für das Abspielen nötigen Kontrollelemente. + /// + public class HtmlElementMultimediaVideo : HtmlElement, IHtmlElementMultimedia + { + /// + /// Liefert oder setzt die Video-Url + /// + public string Src + { + get => GetAttribute("src"); + set => SetAttribute("src", value); + } + + /// + /// Constructor + /// + public HtmlElementMultimediaVideo() + : base("video", false) + { + + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementRootHtml.cs b/src/WebExpress/WebHtml/HtmlElementRootHtml.cs new file mode 100644 index 0000000..d3549eb --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementRootHtml.cs @@ -0,0 +1,64 @@ +using System.Text; + +namespace WebExpress.WebHtml +{ + /// + /// Steht für den Wurzelknoten eines HTML- oder XHTML-Dokuments. Alle weiteren Elemente müssen Nachkommen dieses Elements sein. + /// + public class HtmlElementRootHtml : HtmlElement, IHtmlElementRoot + { + /// + /// Liefert oder setzt den Kopf + /// + public HtmlElementMetadataHead Head { get; private set; } + + /// + /// Liefert oder setzt den Body + /// + public HtmlElementSectionBody Body { get; private set; } + + /// + /// Constructor + /// + public HtmlElementRootHtml() + : base("html") + { + Head = new HtmlElementMetadataHead(); + Body = new HtmlElementSectionBody(); + } + + /// + /// Convert to a string using a StringBuilder. + /// + /// The string builder. + /// The call depth. + public override void ToString(StringBuilder builder, int deep) + { + builder.Append("<"); + builder.Append(ElementName); + builder.Append(">"); + + Head.ToString(builder, deep + 1); + Body.ToString(builder, deep + 1); + + builder.AppendLine(); + builder.Append(""); + builder.Append("\n"); + } + + /// + /// In String konvertieren + /// + /// Das Objekt als String + public override string ToString() + { + var builder = new StringBuilder(); + builder.AppendLine(""); + ToString(builder, 0); + + return builder.ToString(); + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementScriptingCanvas.cs b/src/WebExpress/WebHtml/HtmlElementScriptingCanvas.cs new file mode 100644 index 0000000..6f3c47a --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementScriptingCanvas.cs @@ -0,0 +1,37 @@ +using System; + +namespace WebExpress.WebHtml +{ + /// + /// Steht für einen Bitmap-Bereich, der von Skripts verwendet werden kann, um beispielsweise Diagramme, Spielegraphiken oder andere visuellen Effekte dynamisch darzustellen. + /// + public class HtmlElementScriptingCanvas : HtmlElement, IHtmlElementScripting + { + /// + /// Returns or sets the width. + /// + public int Width + { + get => Convert.ToInt32(GetAttribute("width")); + set => SetAttribute("width", value.ToString()); + } + + /// + /// Returns or sets the width. + /// + public int Height + { + get => Convert.ToInt32(GetAttribute("height")); + set => SetAttribute("height", value.ToString()); + } + + /// + /// Constructor + /// + public HtmlElementScriptingCanvas() + : base("canvas", false) + { + + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementScriptingNoscript.cs b/src/WebExpress/WebHtml/HtmlElementScriptingNoscript.cs new file mode 100644 index 0000000..d6f05c5 --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementScriptingNoscript.cs @@ -0,0 +1,44 @@ +using System.Collections.Generic; + +namespace WebExpress.WebHtml +{ + /// + /// Definiert alternative Inhalte, die angezeigt werden sollen, wenn der Browser kein Skripting unterstützt. + /// + public class HtmlElementScriptingNoscript : HtmlElement, IHtmlElementScripting + { + /// + /// Returns the elements. + /// + public new List Elements => base.Elements; + + /// + /// Constructor + /// + public HtmlElementScriptingNoscript() + : base("span") + { + + } + + /// + /// Constructor + /// + /// 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); + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementScriptingScript.cs b/src/WebExpress/WebHtml/HtmlElementScriptingScript.cs new file mode 100644 index 0000000..fdc91f7 --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementScriptingScript.cs @@ -0,0 +1,82 @@ +using System.Text; + +namespace WebExpress.WebHtml +{ + /// + /// Definiert entweder ein internes Skript oder einen Link auf ein externes Skript. Als Programmiersprache wird JavaScript verwendet. + /// + public class HtmlElementScriptingScript : HtmlElement, IHtmlElementScripting + { + /// + /// Returns or sets the text. + /// + public string Code { get; set; } + + /// + /// Liefert oder setzt die Scriptsprache + /// + public string Language + { + get => GetAttribute("language"); + set => SetAttribute("language", value); + } + + /// + /// Liefert oder setzt den Medientyp + /// + public string Type + { + get => GetAttribute("type"); + set => SetAttribute("type", value); + } + + /// + /// Liefert oder setzt den Link auf die Scriptdatei + /// + public string Src + { + get => GetAttribute("src"); + set => SetAttribute("src", value); + } + + /// + /// Constructor + /// + public HtmlElementScriptingScript() + : base("script") + { + Type = "text/javascript"; + } + + /// + /// Constructor + /// + /// The text. + public HtmlElementScriptingScript(string code) + : this() + { + Code = code; + } + + /// + /// Convert to a string using a StringBuilder. + /// + /// The string builder. + /// The call depth. + public override void ToString(StringBuilder builder, int deep) + { + ToPreString(builder, deep); + + if (!string.IsNullOrWhiteSpace(Code)) + { +#if DEBUG + builder.Append(Code); +#else + builder.Append(Code.Replace("\r", "").Replace("\n", "")); +#endif + } + + ToPostString(builder, deep, false); + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementSectionAddress.cs b/src/WebExpress/WebHtml/HtmlElementSectionAddress.cs new file mode 100644 index 0000000..3be06f3 --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementSectionAddress.cs @@ -0,0 +1,44 @@ +using System.Collections.Generic; + +namespace WebExpress.WebHtml +{ + /// + /// Definiert einen Abschnitt mit Kontaktinformationen. + /// + public class HtmlElementSectionAddress : HtmlElement, IHtmlElementSection + { + /// + /// Returns the elements. + /// + public new List Elements => base.Elements; + + /// + /// Constructor + /// + public HtmlElementSectionAddress() + : base("address") + { + + } + + /// + /// Constructor + /// + /// 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); + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementSectionArticle.cs b/src/WebExpress/WebHtml/HtmlElementSectionArticle.cs new file mode 100644 index 0000000..49f3ad4 --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementSectionArticle.cs @@ -0,0 +1,44 @@ +using System.Collections.Generic; + +namespace WebExpress.WebHtml +{ + /// + /// Beschreibt eigenständigen Inhalt, der unabhängig von den übrigen Inhalten sein kann. + /// + public class HtmlElementSectionArticle : HtmlElement, IHtmlElementSection + { + /// + /// Returns the elements. + /// + public new List Elements => base.Elements; + + /// + /// Constructor + /// + public HtmlElementSectionArticle() + : base("article") + { + + } + + /// + /// Constructor + /// + /// 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); + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementSectionAside.cs b/src/WebExpress/WebHtml/HtmlElementSectionAside.cs new file mode 100644 index 0000000..dcdf0b3 --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementSectionAside.cs @@ -0,0 +1,44 @@ +using System.Collections.Generic; + +namespace WebExpress.WebHtml +{ + /// + /// Steht für eine Randbemerkung. Der übrige Inhalt sollte auch verständlich sein, wenn dieses Element entfernt wird. + /// + public class HtmlElementSectionAside : HtmlElement, IHtmlElementSection + { + /// + /// Returns the elements. + /// + public new List Elements => base.Elements; + + /// + /// Constructor + /// + public HtmlElementSectionAside() + : base("aside") + { + + } + + /// + /// Constructor + /// + /// 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); + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementSectionBody.cs b/src/WebExpress/WebHtml/HtmlElementSectionBody.cs new file mode 100644 index 0000000..dc18d99 --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementSectionBody.cs @@ -0,0 +1,92 @@ +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace WebExpress.WebHtml +{ + /// + /// Steht für den Hauptinhalt eines HTML-Dokuments. Jedes Dokument kann nur ein -Element enthalten. + /// + public class HtmlElementSectionBody : HtmlElement, IHtmlElementSection + { + /// + /// Returns the elements. + /// + public new List Elements => base.Elements; + + /// + /// Liefert oder setzt die Script-Elemente + /// + public List Scripts { get; set; } + + /// + /// Liefert oder setzt das text/javascript + /// + public List ScriptLinks + { + get => (from x in ElementScriptLinks select x.Src).ToList(); + set + { + ElementScriptLinks.Clear(); + ElementScriptLinks.AddRange(from x in value + select new HtmlElementScriptingScript() + { + Language = "javascript", + Src = x, + Type = "text/javascript" + }); + } + } + + /// + /// Liefert oder setzt die externen Scripts + /// + private List ElementScriptLinks { get; set; } + + /// + /// Constructor + /// + public HtmlElementSectionBody() + : base("body") + { + ElementScriptLinks = new List(); + } + + /// + /// Constructor + /// + /// The content of the html element. + public HtmlElementSectionBody(params IHtmlNode[] nodes) + : this() + { + Elements.AddRange(nodes); + } + + /// + /// Convert to a string using a StringBuilder. + /// + /// The string builder. + /// The call depth. + public override void ToString(StringBuilder builder, int deep) + { + ToPreString(builder, deep); + + foreach (var v in Elements.Where(x => x != null)) + { + v.ToString(builder, deep + 1); + } + + foreach (var v in ElementScriptLinks) + { + v.ToString(builder, deep + 1); + } + + foreach (var script in Scripts) + { + new HtmlElementScriptingScript(script).ToString(builder, deep + 1); + } + + ToPostString(builder, deep, true); + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementSectionFooter.cs b/src/WebExpress/WebHtml/HtmlElementSectionFooter.cs new file mode 100644 index 0000000..aadc1dc --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementSectionFooter.cs @@ -0,0 +1,64 @@ +using System.Collections.Generic; +using System.Linq; + +namespace WebExpress.WebHtml +{ + /// + /// Definiert den Fußteil einer Seite oder eines Abschnitts. Er enthält oft Copyright-Hinweise, einen Link auf das Impressum oder Kontaktadressen. + /// + public class HtmlElementSectionFooter : HtmlElement, IHtmlElementSection + { + /// + /// Returns the elements. + /// + public new List Elements => base.Elements; + + /// + /// Returns or sets the text. + /// + 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)); } + } + + /// + /// Constructor + /// + public HtmlElementSectionFooter() + : base("footer") + { + + } + + /// + /// Constructor + /// + /// The content of the html element. + public HtmlElementSectionFooter(string text) + : this() + { + Text = text; + } + + /// + /// Constructor + /// + /// 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); + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementSectionH1.cs b/src/WebExpress/WebHtml/HtmlElementSectionH1.cs new file mode 100644 index 0000000..816c3ae --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementSectionH1.cs @@ -0,0 +1,59 @@ +using System.Linq; +using System.Text; + +namespace WebExpress.WebHtml +{ + /// + /// Markiert eine Überschrift der obersten Ebene + /// + public class HtmlElementSectionH1 : HtmlElement, IHtmlElementSection + { + /// + /// Returns or sets the text. + /// + 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)); } + } + + /// + /// Constructor + /// + public HtmlElementSectionH1() + : base("h1") + { + + } + + /// + /// Constructor + /// + /// The content of the html element. + public HtmlElementSectionH1(string text) + : this() + { + Text = text; + } + + /// + /// Constructor + /// + /// The content of the html element. + public HtmlElementSectionH1(params IHtmlNode[] nodes) + : this() + { + Elements.AddRange(nodes); + } + + /// + /// Convert to a string using a StringBuilder. + /// + /// The string builder. + /// The call depth. + public override void ToString(StringBuilder builder, int deep) + { + base.ToString(builder, deep); + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementSectionH2.cs b/src/WebExpress/WebHtml/HtmlElementSectionH2.cs new file mode 100644 index 0000000..ec480df --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementSectionH2.cs @@ -0,0 +1,59 @@ +using System.Linq; +using System.Text; + +namespace WebExpress.WebHtml +{ + /// + /// Beschreibt eine Überschrift der zweiten Ebene + /// + public class HtmlElementSectionH2 : HtmlElement, IHtmlElementSection + { + /// + /// Returns or sets the text. + /// + 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)); } + } + + /// + /// Constructor + /// + public HtmlElementSectionH2() + : base("h2") + { + + } + + /// + /// Constructor + /// + /// The content of the html element. + public HtmlElementSectionH2(string text) + : this() + { + Text = text; + } + + /// + /// Constructor + /// + /// The content of the html element. + public HtmlElementSectionH2(params IHtmlNode[] nodes) + : this() + { + Elements.AddRange(nodes); + } + + /// + /// Convert to a string using a StringBuilder. + /// + /// The string builder. + /// The call depth. + public override void ToString(StringBuilder builder, int deep) + { + base.ToString(builder, deep); + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementSectionH3.cs b/src/WebExpress/WebHtml/HtmlElementSectionH3.cs new file mode 100644 index 0000000..294d8dd --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementSectionH3.cs @@ -0,0 +1,59 @@ +using System.Linq; +using System.Text; + +namespace WebExpress.WebHtml +{ + /// + /// Beschreibt eine Überschrift der dritten Ebene + /// + public class HtmlElementSectionH3 : HtmlElement, IHtmlElementSection + { + /// + /// Returns or sets the text. + /// + 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)); } + } + + /// + /// Constructor + /// + public HtmlElementSectionH3() + : base("h3") + { + + } + + /// + /// Constructor + /// + /// The content of the html element. + public HtmlElementSectionH3(string text) + : this() + { + Text = text; + } + + /// + /// Constructor + /// + /// The content of the html element. + public HtmlElementSectionH3(params IHtmlNode[] nodes) + : this() + { + Elements.AddRange(nodes); + } + + /// + /// Convert to a string using a StringBuilder. + /// + /// The string builder. + /// The call depth. + public override void ToString(StringBuilder builder, int deep) + { + base.ToString(builder, deep); + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementSectionH4.cs b/src/WebExpress/WebHtml/HtmlElementSectionH4.cs new file mode 100644 index 0000000..981653a --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementSectionH4.cs @@ -0,0 +1,59 @@ +using System.Linq; +using System.Text; + +namespace WebExpress.WebHtml +{ + /// + /// Beschreibt eine Überschrift der vierten Ebene + /// + public class HtmlElementSectionH4 : HtmlElement, IHtmlElementSection + { + /// + /// Returns or sets the text. + /// + 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)); } + } + + /// + /// Constructor + /// + public HtmlElementSectionH4() + : base("h4") + { + + } + + /// + /// Constructor + /// + /// The content of the html element. + public HtmlElementSectionH4(string text) + : this() + { + Text = text; + } + + /// + /// Constructor + /// + /// The content of the html element. + public HtmlElementSectionH4(params IHtmlNode[] nodes) + : this() + { + Elements.AddRange(nodes); + } + + /// + /// Convert to a string using a StringBuilder. + /// + /// The string builder. + /// The call depth. + public override void ToString(StringBuilder builder, int deep) + { + base.ToString(builder, deep); + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementSectionH5.cs b/src/WebExpress/WebHtml/HtmlElementSectionH5.cs new file mode 100644 index 0000000..09c7986 --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementSectionH5.cs @@ -0,0 +1,59 @@ +using System.Linq; +using System.Text; + +namespace WebExpress.WebHtml +{ + /// + /// Beschreibt eine Überschrift der fünften Ebene + /// + public class HtmlElementSectionH5 : HtmlElement, IHtmlElementSection + { + /// + /// Returns or sets the text. + /// + 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)); } + } + + /// + /// Constructor + /// + public HtmlElementSectionH5() + : base("h5") + { + + } + + /// + /// Constructor + /// + /// The content of the html element. + public HtmlElementSectionH5(string text) + : this() + { + Text = text; + } + + /// + /// Constructor + /// + /// The content of the html element. + public HtmlElementSectionH5(params IHtmlNode[] nodes) + : this() + { + Elements.AddRange(nodes); + } + + /// + /// Convert to a string using a StringBuilder. + /// + /// The string builder. + /// The call depth. + public override void ToString(StringBuilder builder, int deep) + { + base.ToString(builder, deep); + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementSectionH6.cs b/src/WebExpress/WebHtml/HtmlElementSectionH6.cs new file mode 100644 index 0000000..b215ceb --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementSectionH6.cs @@ -0,0 +1,59 @@ +using System.Linq; +using System.Text; + +namespace WebExpress.WebHtml +{ + /// + /// Beschreibt eine Überschrift der untersten Ebene + /// + public class HtmlElementSectionH6 : HtmlElement, IHtmlElementSection + { + /// + /// Returns or sets the text. + /// + 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)); } + } + + /// + /// Constructor + /// + public HtmlElementSectionH6() + : base("h6") + { + + } + + /// + /// Constructor + /// + /// The content of the html element. + public HtmlElementSectionH6(string text) + : this() + { + Text = text; + } + + /// + /// Constructor + /// + /// The content of the html element. + public HtmlElementSectionH6(params IHtmlNode[] nodes) + : this() + { + Elements.AddRange(nodes); + } + + /// + /// Convert to a string using a StringBuilder. + /// + /// The string builder. + /// The call depth. + public override void ToString(StringBuilder builder, int deep) + { + base.ToString(builder, deep); + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementSectionHeader.cs b/src/WebExpress/WebHtml/HtmlElementSectionHeader.cs new file mode 100644 index 0000000..1517cc3 --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementSectionHeader.cs @@ -0,0 +1,44 @@ +using System.Collections.Generic; + +namespace WebExpress.WebHtml +{ + /// + /// Definiert den Kopfteil einer Seite oder eines Abschnitts. Er enthält oft ein Logo, den Titel der Website und die Seitennavigation. + /// + public class HtmlElementSectionHeader : HtmlElement, IHtmlElementSection + { + /// + /// Returns the elements. + /// + public new List Elements => base.Elements; + + /// + /// Constructor + /// + public HtmlElementSectionHeader() + : base("header") + { + + } + + /// + /// Constructor + /// + /// 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); + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementSectionMain.cs b/src/WebExpress/WebHtml/HtmlElementSectionMain.cs new file mode 100644 index 0000000..df75678 --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementSectionMain.cs @@ -0,0 +1,44 @@ +using System.Collections.Generic; + +namespace WebExpress.WebHtml +{ + /// + /// Definiert den Hauptinhalt der Seite. Es ist nur ein
Element pro Seite zulässig. + ///
+ public class HtmlElementSectionMain : HtmlElement, IHtmlElementSection + { + /// + /// Returns the elements. + /// + public new List Elements => base.Elements; + + /// + /// Constructor + /// + public HtmlElementSectionMain() + : base("main") + { + + } + + /// + /// Constructor + /// + /// 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); + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementSectionNav.cs b/src/WebExpress/WebHtml/HtmlElementSectionNav.cs new file mode 100644 index 0000000..067b26b --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementSectionNav.cs @@ -0,0 +1,43 @@ +using System.Collections.Generic; + +namespace WebExpress.WebHtml +{ + /// + /// Das Element nav beschreibt eine Aufzählungsliste von Navigationslinks. + /// + public class HtmlElementSectionNav : HtmlElement + { + /// + /// Returns the elements. + /// + public new List Elements => base.Elements; + + /// + /// Constructor + /// + public HtmlElementSectionNav() + : base("nav") + { + } + + /// + /// Constructor + /// + /// 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); + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementSectionSection.cs b/src/WebExpress/WebHtml/HtmlElementSectionSection.cs new file mode 100644 index 0000000..d89825f --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementSectionSection.cs @@ -0,0 +1,44 @@ +using System.Collections.Generic; + +namespace WebExpress.WebHtml +{ + /// + /// Beschreibt einen Abschnitt eines Dokuments. + /// + public class HtmlElementSectionSection : HtmlElement, IHtmlElementSection + { + /// + /// Returns the elements. + /// + public new List Elements => base.Elements; + + /// + /// Constructor + /// + public HtmlElementSectionSection() + : base("section") + { + + } + + /// + /// Constructor + /// + /// 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); + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementTableCaption.cs b/src/WebExpress/WebHtml/HtmlElementTableCaption.cs new file mode 100644 index 0000000..12e066c --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementTableCaption.cs @@ -0,0 +1,59 @@ +using System.Linq; +using System.Text; + +namespace WebExpress.WebHtml +{ + /// + /// Kennzeichnet die Beschriftung (Titel) einer Tabelle. + /// + public class HtmlElementTableCaption : HtmlElement, IHtmlElementTable + { + /// + /// Returns or sets the text. + /// + 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)); } + } + + /// + /// Constructor + /// + public HtmlElementTableCaption() + : base("caption") + { + + } + + /// + /// Constructor + /// + /// The content of the html element. + public HtmlElementTableCaption(string text) + : this() + { + Text = text; + } + + /// + /// Constructor + /// + /// The content of the html element. + public HtmlElementTableCaption(params IHtmlNode[] nodes) + : this() + { + Elements.AddRange(nodes); + } + + /// + /// Convert to a string using a StringBuilder. + /// + /// The string builder. + /// The call depth. + public override void ToString(StringBuilder builder, int deep) + { + base.ToString(builder, deep); + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementTableCol.cs b/src/WebExpress/WebHtml/HtmlElementTableCol.cs new file mode 100644 index 0000000..d706e70 --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementTableCol.cs @@ -0,0 +1,17 @@ +namespace WebExpress.WebHtml +{ + /// + /// Steht für eine Tabellenspalte. + /// + public class HtmlElementTableCol : HtmlElement, IHtmlElementTable + { + /// + /// Constructor + /// + public HtmlElementTableCol() + : base("col") + { + + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementTableColgroup.cs b/src/WebExpress/WebHtml/HtmlElementTableColgroup.cs new file mode 100644 index 0000000..bf43af3 --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementTableColgroup.cs @@ -0,0 +1,17 @@ +namespace WebExpress.WebHtml +{ + /// + /// Steht für eine Gruppe aus einer oder mehreren Tabellenspalten. + /// + public class HtmlElementTableColgroup : HtmlElement, IHtmlElementTable + { + /// + /// Constructor + /// + public HtmlElementTableColgroup() + : base("colgroup") + { + + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementTableTable.cs b/src/WebExpress/WebHtml/HtmlElementTableTable.cs new file mode 100644 index 0000000..98ecca4 --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementTableTable.cs @@ -0,0 +1,49 @@ +using System.Collections.Generic; +using System.Text; + +namespace WebExpress.WebHtml +{ + /// + /// Markiert eine Tabelle, d.h. Daten mit mehr als einer Dimension. + /// + public class HtmlElementTableTable : HtmlElement, IHtmlElementTable + { + /// + /// Liefert oder setzt die Spalten + /// + public HtmlElementTableTr Columns { get; set; } + + /// + /// Liefert oder setzt die Zeilen + /// + public List Rows { get; private set; } + + /// + /// Constructor + /// + public HtmlElementTableTable() + : base("table") + { + Columns = new HtmlElementTableTr(); + Rows = new List(); + } + + /// + /// Convert to a string using a StringBuilder. + /// + /// The string builder. + /// The call depth. + public override void ToString(StringBuilder builder, int deep) + { + ToPreString(builder, deep); + + var column = new HtmlElementTableThead(Columns); + column.ToString(builder, deep + 1); + + var body = new HtmlElementTableTbody(Rows); + body.ToString(builder, deep + 1); + + ToPostString(builder, deep); + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementTableTbody.cs b/src/WebExpress/WebHtml/HtmlElementTableTbody.cs new file mode 100644 index 0000000..e5e976f --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementTableTbody.cs @@ -0,0 +1,38 @@ +using System.Collections.Generic; + +namespace WebExpress.WebHtml +{ + /// + /// Steht für die Spalten, die die eigentlichen Daten einer Tabelle enthalten. + /// + public class HtmlElementTableTbody : HtmlElement, IHtmlElementTable + { + /// + /// Constructor + /// + public HtmlElementTableTbody() + : base("tbody") + { + } + + /// + /// Constructor + /// + /// 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); + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementTableTd.cs b/src/WebExpress/WebHtml/HtmlElementTableTd.cs new file mode 100644 index 0000000..668ea22 --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementTableTd.cs @@ -0,0 +1,48 @@ +using System.Collections.Generic; + +namespace WebExpress.WebHtml +{ + /// + /// Kennzeichnet eine einzelne Tabellenzelle. + /// + public class HtmlElementTableTd : HtmlElement, IHtmlElementTable + { + /// + /// Constructor + /// + public HtmlElementTableTd() + : base("td") + { + } + + /// + /// Constructor + /// + /// The content of the html element. + public HtmlElementTableTd(IHtmlNode node) + : this() + { + Elements.Add(node); + } + + /// + /// Constructor + /// + /// 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); + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementTableTfoot.cs b/src/WebExpress/WebHtml/HtmlElementTableTfoot.cs new file mode 100644 index 0000000..2100abe --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementTableTfoot.cs @@ -0,0 +1,38 @@ +using System.Collections.Generic; + +namespace WebExpress.WebHtml +{ + /// + /// Markiert die Gruppe der Tabellenzeilen, die die Zusammenfassungen der Tabellenspalten enthalten. + /// + public class HtmlElementTableTfoot : HtmlElement, IHtmlElementTable + { + /// + /// Constructor + /// + public HtmlElementTableTfoot() + : base("tfoot") + { + } + + /// + /// Constructor + /// + /// 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); + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementTableTh.cs b/src/WebExpress/WebHtml/HtmlElementTableTh.cs new file mode 100644 index 0000000..85d1869 --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementTableTh.cs @@ -0,0 +1,38 @@ +using System.Collections.Generic; + +namespace WebExpress.WebHtml +{ + /// + /// Kennzeichnet eine Tabellenzelle mit einer Beschriftung. + /// + public class HtmlElementTableTh : HtmlElement, IHtmlElementTable + { + /// + /// Constructor + /// + public HtmlElementTableTh() + : base("th") + { + } + + /// + /// Constructor + /// + /// 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); + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementTableThead.cs b/src/WebExpress/WebHtml/HtmlElementTableThead.cs new file mode 100644 index 0000000..ee922f4 --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementTableThead.cs @@ -0,0 +1,38 @@ +using System.Collections.Generic; + +namespace WebExpress.WebHtml +{ + /// + /// Markiert die Gruppe der Tabellenzeilen, die die Beschriftungen der Tabellenspalten enthalten. + /// + public class HtmlElementTableThead : HtmlElement, IHtmlElementTable + { + /// + /// Constructor + /// + public HtmlElementTableThead() + : base("thead") + { + } + + /// + /// Constructor + /// + /// 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); + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementTableTr.cs b/src/WebExpress/WebHtml/HtmlElementTableTr.cs new file mode 100644 index 0000000..2fcce9a --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementTableTr.cs @@ -0,0 +1,38 @@ +using System.Collections.Generic; + +namespace WebExpress.WebHtml +{ + /// + /// Steht für eine Zeile mit Tabellenzellen. + /// + public class HtmlElementTableTr : HtmlElement, IHtmlElementTable + { + /// + /// Constructor + /// + public HtmlElementTableTr() + : base("tr") + { + } + + /// + /// Constructor + /// + /// 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); + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementTextContentBlockquote.cs b/src/WebExpress/WebHtml/HtmlElementTextContentBlockquote.cs new file mode 100644 index 0000000..8a8b483 --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementTextContentBlockquote.cs @@ -0,0 +1,44 @@ +using System.Collections.Generic; + +namespace WebExpress.WebHtml +{ + /// + /// Markiert ein Zitat. + /// + public class HtmlElementTextContentBlockquote : HtmlElement, IHtmlElementTextContent + { + /// + /// Returns the elements. + /// + public new List Elements => base.Elements; + + /// + /// Constructor + /// + public HtmlElementTextContentBlockquote() + : base("blockquote") + { + + } + + /// + /// Constructor + /// + /// 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); + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementTextContentDd.cs b/src/WebExpress/WebHtml/HtmlElementTextContentDd.cs new file mode 100644 index 0000000..73a8a15 --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementTextContentDd.cs @@ -0,0 +1,44 @@ +using System.Collections.Generic; + +namespace WebExpress.WebHtml +{ + /// + /// Markiert die Definition des oder der Begriffe, die in den direkt vorangehenden
-Element angegeben wurden. + ///
+ public class HtmlElementTextContentDd : HtmlElement, IHtmlElementTextContent + { + /// + /// Returns the elements. + /// + public new List Elements => base.Elements; + + /// + /// Constructor + /// + public HtmlElementTextContentDd() + : base("dd") + { + + } + + /// + /// Constructor + /// + /// 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); + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementTextContentDiv.cs b/src/WebExpress/WebHtml/HtmlElementTextContentDiv.cs new file mode 100644 index 0000000..17484cb --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementTextContentDiv.cs @@ -0,0 +1,44 @@ +using System.Collections.Generic; + +namespace WebExpress.WebHtml +{ + /// + /// Bezeichnet ein allgemeines Container-Element ohne spezielle semantische Bedeutung. + /// + public class HtmlElementTextContentDiv : HtmlElement + { + /// + /// Returns the elements. + /// + public new List Elements => base.Elements; + + /// + /// Constructor + /// + public HtmlElementTextContentDiv() + : base("div") + { + + } + + /// + /// Constructor + /// + /// 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); + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementTextContentDl.cs b/src/WebExpress/WebHtml/HtmlElementTextContentDl.cs new file mode 100644 index 0000000..f72ab6f --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementTextContentDl.cs @@ -0,0 +1,44 @@ +using System.Collections.Generic; + +namespace WebExpress.WebHtml +{ + /// + /// Kennzeichnet eine Definitionsliste aus Begriffen und den dazugehörigen Definitionen. + /// + public class HtmlElementTextContentDl : HtmlElement, IHtmlElementTextContent + { + /// + /// Returns the elements. + /// + public new List Elements => base.Elements; + + /// + /// Constructor + /// + public HtmlElementTextContentDl() + : base("dl") + { + + } + + /// + /// Constructor + /// + /// 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); + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementTextContentDt.cs b/src/WebExpress/WebHtml/HtmlElementTextContentDt.cs new file mode 100644 index 0000000..bf72e71 --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementTextContentDt.cs @@ -0,0 +1,44 @@ +using System.Collections.Generic; + +namespace WebExpress.WebHtml +{ + /// + /// Kennzeichnet einen Begriff der im folgenden
-Element beschrieben wird. + ///
+ public class HtmlElementTextContentDt : HtmlElement, IHtmlElementTextContent + { + /// + /// Returns the elements. + /// + public new List Elements => base.Elements; + + /// + /// Constructor + /// + public HtmlElementTextContentDt() + : base("dt") + { + + } + + /// + /// Constructor + /// + /// 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); + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementTextContentFigcaption.cs b/src/WebExpress/WebHtml/HtmlElementTextContentFigcaption.cs new file mode 100644 index 0000000..97c4d2c --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementTextContentFigcaption.cs @@ -0,0 +1,44 @@ +using System.Collections.Generic; + +namespace WebExpress.WebHtml +{ + /// + /// Markiert die Beschriftung einer Abbildung. + /// + public class HtmlElementTextContentFigcaption : HtmlElement, IHtmlElementTextContent + { + /// + /// Returns the elements. + /// + public new List Elements => base.Elements; + + /// + /// Constructor + /// + public HtmlElementTextContentFigcaption() + : base("figcaption") + { + + } + + /// + /// Constructor + /// + /// 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); + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementTextContentFigure.cs b/src/WebExpress/WebHtml/HtmlElementTextContentFigure.cs new file mode 100644 index 0000000..3a245fa --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementTextContentFigure.cs @@ -0,0 +1,44 @@ +using System.Collections.Generic; + +namespace WebExpress.WebHtml +{ + /// + /// Kennzeichnet eine Abbildung, die einen Teil des Dokuments illustriert. + /// + public class HtmlElementTextContentFigure : HtmlElement, IHtmlElementTextContent + { + /// + /// Returns the elements. + /// + public new List Elements => base.Elements; + + /// + /// Constructor + /// + public HtmlElementTextContentFigure() + : base("figure") + { + + } + + /// + /// Constructor + /// + /// 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); + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementTextContentHr.cs b/src/WebExpress/WebHtml/HtmlElementTextContentHr.cs new file mode 100644 index 0000000..cfbb4e3 --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementTextContentHr.cs @@ -0,0 +1,29 @@ +using System.Text; + +namespace WebExpress.WebHtml +{ + /// + /// Bezeichnet einen thematischen Bruch zwischen Absätzen eines Abschnitts, Artikels oder anderem längeren Inhalt. + /// + public class HtmlElementTextContentHr : HtmlElement, IHtmlElementTextContent + { + /// + /// Constructor + /// + public HtmlElementTextContentHr() + : base("hr", false) + { + + } + + /// + /// Convert to a string using a StringBuilder. + /// + /// The string builder. + /// The call depth. + public override void ToString(StringBuilder builder, int deep) + { + base.ToString(builder, deep); + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementTextContentLi.cs b/src/WebExpress/WebHtml/HtmlElementTextContentLi.cs new file mode 100644 index 0000000..75caa0d --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementTextContentLi.cs @@ -0,0 +1,43 @@ +using System.Collections.Generic; + +namespace WebExpress.WebHtml +{ + /// + /// Das Element li beschreibt ein Element einer ungeordneten oder geordneten Liste + /// + public class HtmlElementTextContentLi : HtmlElement, IHtmlElementTextContent + { + /// + /// Returns the elements. + /// + public new List Elements => base.Elements; + + /// + /// Constructor + /// + public HtmlElementTextContentLi() + : base("li") + { + } + + /// + /// Constructor + /// + /// 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); + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementTextContentOl.cs b/src/WebExpress/WebHtml/HtmlElementTextContentOl.cs new file mode 100644 index 0000000..e076a4d --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementTextContentOl.cs @@ -0,0 +1,49 @@ +using System.Collections.Generic; +using System.Text; + +namespace WebExpress.WebHtml +{ + /// + /// Das Element ul beschreibt eine Aufzählungsliste, also eine Liste, bei der die Reihenfolge der Elemente eine Rolle spielt. + /// ol steht dabei für ordered list, geordnete, sortierte Liste. + /// + public class HtmlElementTextContentOl : HtmlElement, IHtmlElementTextContent + { + /// + /// Returns the elements. + /// + public new List Elements { get; set; } + + /// + /// Constructor + /// + public HtmlElementTextContentOl() + : base("ol") + { + Elements = new List(); + } + + /// + /// Constructor + /// + /// The content of the html element. + public HtmlElementTextContentOl(params HtmlElementTextContentLi[] nodes) + : this() + { + Elements.AddRange(nodes); + } + + /// + /// Convert to a string using a StringBuilder. + /// + /// The string builder. + /// 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/WebHtml/HtmlElementTextContentP.cs b/src/WebExpress/WebHtml/HtmlElementTextContentP.cs new file mode 100644 index 0000000..e7fa8a6 --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementTextContentP.cs @@ -0,0 +1,59 @@ +using System.Linq; +using System.Text; + +namespace WebExpress.WebHtml +{ + /// + /// Markiert den Inhalt als Absatz. + /// + public class HtmlElementTextContentP : HtmlElement, IHtmlElementTextContent + { + /// + /// Returns or sets the text. + /// + 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)); } + } + + /// + /// Constructor + /// + public HtmlElementTextContentP() + : base("p") + { + + } + + /// + /// Constructor + /// + /// The content of the html element. + public HtmlElementTextContentP(string text) + : this() + { + Text = text; + } + + /// + /// Constructor + /// + /// The content of the html element. + public HtmlElementTextContentP(params IHtmlNode[] nodes) + : this() + { + Elements.AddRange(nodes); + } + + /// + /// Convert to a string using a StringBuilder. + /// + /// The string builder. + /// The call depth. + public override void ToString(StringBuilder builder, int deep) + { + base.ToString(builder, deep); + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementTextContentPre.cs b/src/WebExpress/WebHtml/HtmlElementTextContentPre.cs new file mode 100644 index 0000000..0c9704a --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementTextContentPre.cs @@ -0,0 +1,44 @@ +using System.Collections.Generic; + +namespace WebExpress.WebHtml +{ + /// + /// Markiert den Inhalt dieses Elements als vorformatiert und das dieses Format erhalten bleiben soll. + /// + public class HtmlElementTextContentPre : HtmlElement, IHtmlElementTextContent + { + /// + /// Returns the elements. + /// + public new List Elements => base.Elements; + + /// + /// Constructor + /// + public HtmlElementTextContentPre() + : base("pre") + { + + } + + /// + /// Constructor + /// + /// 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); + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementTextContentUl.cs b/src/WebExpress/WebHtml/HtmlElementTextContentUl.cs new file mode 100644 index 0000000..5ace8d8 --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementTextContentUl.cs @@ -0,0 +1,44 @@ +using System.Collections.Generic; + +namespace WebExpress.WebHtml +{ + /// + /// Das Element ul beschreibt eine Aufzählungsliste, also eine Liste, bei der die Reihenfolge der Elemente nur eine + /// untergeordnete oder keine Rolle spielt. ul steht dabei für unordered list, ungeordnete, unsortierte Liste. + /// + public class HtmlElementTextContentUl : HtmlElement, IHtmlElementTextContent + { + /// + /// Returns the elements. + /// + public new List Elements => base.Elements; + + /// + /// Constructor + /// + public HtmlElementTextContentUl() + : base("ul") + { + } + + /// + /// Constructor + /// + /// 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); + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementTextSemanticsA.cs b/src/WebExpress/WebHtml/HtmlElementTextSemanticsA.cs new file mode 100644 index 0000000..0cade4a --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementTextSemanticsA.cs @@ -0,0 +1,101 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace WebExpress.WebHtml +{ + /// + /// ezeichnet einen Hyperlink, welcher auf eine andere Ressource verweist. + /// + public class HtmlElementTextSemanticsA : HtmlElement, IHtmlElementTextSemantics + { + /// + /// Returns the elements. + /// + public new List Elements => base.Elements; + + /// + /// Returns or sets the text. + /// + 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)); } + } + + /// + /// Liefert oder setzt den alternativen Text + /// + public string Alt + { + get => GetAttribute("alt"); + set => SetAttribute("alt", value); + } + + /// + /// Returns or sets the tooltip. + /// + public string Title + { + get => GetAttribute("title"); + set => SetAttribute("title", value); + } + + /// + /// Liefert oder setzt die Ziel-Url + /// + public string Href + { + get => GetAttribute("href"); + set => SetAttribute("href", value); + } + + /// + /// Liefert oder setzt das Ziel + /// + public TypeTarget Target + { + get => (TypeTarget)Enum.Parse(typeof(TypeTarget), GetAttribute("target")); + set => SetAttribute("target", value.ToStringValue()); + } + + /// + /// Constructor + /// + public HtmlElementTextSemanticsA() + : base("a") + { + + } + + /// + /// Constructor + /// + /// The content of the html element. + public HtmlElementTextSemanticsA(string text) + : this() + { + Text = text; + } + + /// + /// Constructor + /// + /// 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); + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementTextSemanticsAbbr.cs b/src/WebExpress/WebHtml/HtmlElementTextSemanticsAbbr.cs new file mode 100644 index 0000000..1447fc3 --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementTextSemanticsAbbr.cs @@ -0,0 +1,44 @@ +using System.Collections.Generic; + +namespace WebExpress.WebHtml +{ + /// + /// Markiert eine Abkürzung oder ein Akronym. + /// + public class HtmlElementTextSemanticsAbbr : HtmlElement, IHtmlElementTextSemantics + { + /// + /// Returns the elements. + /// + public new List Elements => base.Elements; + + /// + /// Constructor + /// + public HtmlElementTextSemanticsAbbr() + : base("abbr") + { + + } + + /// + /// Constructor + /// + /// 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); + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementTextSemanticsB.cs b/src/WebExpress/WebHtml/HtmlElementTextSemanticsB.cs new file mode 100644 index 0000000..c78a972 --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementTextSemanticsB.cs @@ -0,0 +1,59 @@ +using System.Linq; +using System.Text; + +namespace WebExpress.WebHtml +{ + /// + /// Steht für einen Textabschnitt, der vom übrigen Inhalt abgesetzt und üblicherweise fettgedruckt dargestellt wird, ohne für eine spezielle Betonung oder Wichtigkeit zu stehen. Dies kann beispielsweise ein Schlüsselwort oder ein Produktname in einer Produktbewertung sein. + /// + public class HtmlElementTextSemanticsB : HtmlElement, IHtmlElementTextSemantics + { + /// + /// Returns or sets the text. + /// + 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)); } + } + + /// + /// Constructor + /// + public HtmlElementTextSemanticsB() + : base("b") + { + + } + + /// + /// Constructor + /// + /// The content of the html element. + public HtmlElementTextSemanticsB(string text) + : this() + { + Text = text; + } + + /// + /// Constructor + /// + /// The content of the html element. + public HtmlElementTextSemanticsB(params IHtmlNode[] nodes) + : this() + { + Elements.AddRange(nodes); + } + + /// + /// Convert to a string using a StringBuilder. + /// + /// The string builder. + /// The call depth. + public override void ToString(StringBuilder builder, int deep) + { + base.ToString(builder, deep); + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementTextSemanticsBdi.cs b/src/WebExpress/WebHtml/HtmlElementTextSemanticsBdi.cs new file mode 100644 index 0000000..f609078 --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementTextSemanticsBdi.cs @@ -0,0 +1,68 @@ +using System.Linq; +using System.Text; + +namespace WebExpress.WebHtml +{ + /// + /// Markiert Text, der vom umgebenden Inhalt zum Zweck der bidirektionalen Formatierung isoliert werden soll. Hiermit kann ein Textabschnitt mit einer unterschiedlichen oder unbekannten Textrichtung gekennzeichnet werden. + /// + public class HtmlElementTextSemanticsBdi : HtmlElement, IHtmlElementTextSemantics + { + /// + /// Returns or sets the text. + /// + 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)); } + } + + /// + /// Liefert oder setzt die Schreibrichtung + /// + public string Dir + { + get => GetAttribute("dir"); + set => SetAttribute("dir", value); + } + + /// + /// Constructor + /// + public HtmlElementTextSemanticsBdi() + : base("bdi") + { + + } + + /// + /// Constructor + /// + /// The content of the html element. + public HtmlElementTextSemanticsBdi(string text) + : this() + { + Text = text; + } + + /// + /// Constructor + /// + /// The content of the html element. + public HtmlElementTextSemanticsBdi(params IHtmlNode[] nodes) + : this() + { + Elements.AddRange(nodes); + } + + /// + /// Convert to a string using a StringBuilder. + /// + /// The string builder. + /// The call depth. + public override void ToString(StringBuilder builder, int deep) + { + base.ToString(builder, deep); + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementTextSemanticsBdo.cs b/src/WebExpress/WebHtml/HtmlElementTextSemanticsBdo.cs new file mode 100644 index 0000000..a7d4810 --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementTextSemanticsBdo.cs @@ -0,0 +1,68 @@ +using System.Linq; +using System.Text; + +namespace WebExpress.WebHtml +{ + /// + /// Kann verwendet werden, um die Textrichtung der enthaltenen Kindelemente zu steuern. Hiermit kann der Unicode BiDi-Algorithmus explizit überschrieben werden. + /// + public class HtmlElementTextSemanticsBdo : HtmlElement, IHtmlElementTextSemantics + { + /// + /// Returns or sets the text. + /// + 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)); } + } + + /// + /// Liefert oder setzt die Schreibrichtung + /// + public string Dir + { + get => GetAttribute("dir"); + set => SetAttribute("dir", value); + } + + /// + /// Constructor + /// + public HtmlElementTextSemanticsBdo() + : base("bdo") + { + + } + + /// + /// Constructor + /// + /// The content of the html element. + public HtmlElementTextSemanticsBdo(string text) + : this() + { + Text = text; + } + + /// + /// Constructor + /// + /// The content of the html element. + public HtmlElementTextSemanticsBdo(params IHtmlNode[] nodes) + : this() + { + Elements.AddRange(nodes); + } + + /// + /// Convert to a string using a StringBuilder. + /// + /// The string builder. + /// The call depth. + public override void ToString(StringBuilder builder, int deep) + { + base.ToString(builder, deep); + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementTextSemanticsBr.cs b/src/WebExpress/WebHtml/HtmlElementTextSemanticsBr.cs new file mode 100644 index 0000000..58af098 --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementTextSemanticsBr.cs @@ -0,0 +1,16 @@ +namespace WebExpress.WebHtml +{ + /// + /// Bezeichnet einen Zeilenumbruch. + /// + public class HtmlElementTextSemanticsBr : HtmlElement, IHtmlElementTextSemantics + { + /// + /// Constructor + /// + public HtmlElementTextSemanticsBr() + : base("br", false) + { + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementTextSemanticsCite.cs b/src/WebExpress/WebHtml/HtmlElementTextSemanticsCite.cs new file mode 100644 index 0000000..5d94043 --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementTextSemanticsCite.cs @@ -0,0 +1,64 @@ +using System.Collections.Generic; +using System.Linq; + +namespace WebExpress.WebHtml +{ + /// + /// Markiert den Titel eines Werks. + /// + public class HtmlElementTextSemanticsCite : HtmlElement, IHtmlElementTextSemantics + { + /// + /// Returns or sets the text. + /// + 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)); } + } + + /// + /// Returns the elements. + /// + public new List Elements => base.Elements; + + /// + /// Constructor + /// + public HtmlElementTextSemanticsCite() + : base("cite") + { + + } + + /// + /// Constructor + /// + /// The content of the html element. + public HtmlElementTextSemanticsCite(string text) + : this() + { + Text = text; + } + + /// + /// Constructor + /// + /// 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); + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementTextSemanticsCode.cs b/src/WebExpress/WebHtml/HtmlElementTextSemanticsCode.cs new file mode 100644 index 0000000..df87492 --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementTextSemanticsCode.cs @@ -0,0 +1,44 @@ +using System.Collections.Generic; + +namespace WebExpress.WebHtml +{ + /// + /// Markiert ein Programmiercode. + /// + public class HtmlElementTextSemanticsCode : HtmlElement, IHtmlElementTextSemantics + { + /// + /// Returns the elements. + /// + public new List Elements => base.Elements; + + /// + /// Constructor + /// + public HtmlElementTextSemanticsCode() + : base("code") + { + + } + + /// + /// Constructor + /// + /// 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); + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementTextSemanticsData.cs b/src/WebExpress/WebHtml/HtmlElementTextSemanticsData.cs new file mode 100644 index 0000000..ec4101a --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementTextSemanticsData.cs @@ -0,0 +1,54 @@ +using System.Collections.Generic; +using System.Linq; + +namespace WebExpress.WebHtml +{ + /// + /// Verbindet seinen Inhalt mit einem maschinenlesbaren Equivalent, angegeben im value-Attribut. (Dieses Element wird nur in der WHATWG-Version des HTML-Standards definiert, nicht aber in der W3C-Version von HTML5). + /// + public class HtmlElementTextSemanticsData : HtmlElement, IHtmlElementTextSemantics + { + /// + /// Returns the elements. + /// + public new List Elements => base.Elements; + + /// + /// Returns or sets the value. + /// + 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)); } + } + + /// + /// Constructor + /// + public HtmlElementTextSemanticsData() + : base("data") + { + + } + + /// + /// Constructor + /// + /// 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); + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementTextSemanticsDfn.cs b/src/WebExpress/WebHtml/HtmlElementTextSemanticsDfn.cs new file mode 100644 index 0000000..5d6591b --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementTextSemanticsDfn.cs @@ -0,0 +1,44 @@ +using System.Collections.Generic; + +namespace WebExpress.WebHtml +{ + /// + /// Steht für einen Begriff, dessen Definition im nächstgelegenen Nachkommen-Element enthalten ist. + /// + public class HtmlElementTextSemanticsDfn : HtmlElement, IHtmlElementTextSemantics + { + /// + /// Returns the elements. + /// + public new List Elements => base.Elements; + + /// + /// Constructor + /// + public HtmlElementTextSemanticsDfn() + : base("dfn") + { + + } + + /// + /// Constructor + /// + /// 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); + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementTextSemanticsEm.cs b/src/WebExpress/WebHtml/HtmlElementTextSemanticsEm.cs new file mode 100644 index 0000000..c8aa749 --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementTextSemanticsEm.cs @@ -0,0 +1,44 @@ +using System.Collections.Generic; + +namespace WebExpress.WebHtml +{ + /// + /// Markiert einen hervorgehobenen Text. + /// + public class HtmlElementTextSemanticsEm : HtmlElement, IHtmlElementTextSemantics + { + /// + /// Returns the elements. + /// + public new List Elements => base.Elements; + + /// + /// Constructor + /// + public HtmlElementTextSemanticsEm() + : base("em") + { + + } + + /// + /// Constructor + /// + /// 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); + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementTextSemanticsI.cs b/src/WebExpress/WebHtml/HtmlElementTextSemanticsI.cs new file mode 100644 index 0000000..b6c39c7 --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementTextSemanticsI.cs @@ -0,0 +1,59 @@ +using System.Linq; +using System.Text; + +namespace WebExpress.WebHtml +{ + /// + /// Steht für einen Textabschnitt, der vom übrigen Inhalt abgesetzt und üblicherweise kursiv dargestellt wird, ohne für eine spezielle Betonung oder Wichtigkeit zu stehen. Dies kann beispielsweise eine taxonomische Bezeichnung, ein technischer Begriff, ein idiomatischer Ausdruck, ein Gedanke oder der Name eines Schiffes sein. + /// + public class HtmlElementTextSemanticsI : HtmlElement, IHtmlElementTextSemantics + { + /// + /// Returns or sets the text. + /// + 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)); } + } + + /// + /// Constructor + /// + public HtmlElementTextSemanticsI() + : base("i") + { + + } + + /// + /// Constructor + /// + /// The content of the html element. + public HtmlElementTextSemanticsI(string text) + : this() + { + Text = text; + } + + /// + /// Constructor + /// + /// The content of the html element. + public HtmlElementTextSemanticsI(params IHtmlNode[] nodes) + : this() + { + Elements.AddRange(nodes); + } + + /// + /// Convert to a string using a StringBuilder. + /// + /// The string builder. + /// The call depth. + public override void ToString(StringBuilder builder, int deep) + { + base.ToString(builder, deep); + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementTextSemanticsKdb.cs b/src/WebExpress/WebHtml/HtmlElementTextSemanticsKdb.cs new file mode 100644 index 0000000..eb6d201 --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementTextSemanticsKdb.cs @@ -0,0 +1,44 @@ +using System.Collections.Generic; + +namespace WebExpress.WebHtml +{ + /// + /// Markiert eine Benutzereingabe, oftmals, aber nicht unbedingt, auf der Tastatur. Kann auch für andere Eingaben, beispielsweise transkribierte Sprachbefehle stehen. + /// + public class HtmlElementTextSemanticsKdb : HtmlElement, IHtmlElementTextSemantics + { + /// + /// Returns the elements. + /// + public new List Elements => base.Elements; + + /// + /// Constructor + /// + public HtmlElementTextSemanticsKdb() + : base("kdb") + { + + } + + /// + /// Constructor + /// + /// 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); + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementTextSemanticsMark.cs b/src/WebExpress/WebHtml/HtmlElementTextSemanticsMark.cs new file mode 100644 index 0000000..088e235 --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementTextSemanticsMark.cs @@ -0,0 +1,59 @@ +using System.Linq; +using System.Text; + +namespace WebExpress.WebHtml +{ + /// + /// Steht für Text, der aus Referenzgründen hervorgehoben wird, d.h. der in anderem Kontext von Bedeutung ist. + /// + public class HtmlElementTextSemanticsMark : HtmlElement, IHtmlElementTextSemantics + { + /// + /// Returns or sets the text. + /// + 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)); } + } + + /// + /// Constructor + /// + public HtmlElementTextSemanticsMark() + : base("mark") + { + + } + + /// + /// Constructor + /// + /// The content of the html element. + public HtmlElementTextSemanticsMark(string text) + : this() + { + Text = text; + } + + /// + /// Constructor + /// + /// The content of the html element. + public HtmlElementTextSemanticsMark(params IHtmlNode[] nodes) + : this() + { + Elements.AddRange(nodes); + } + + /// + /// Convert to a string using a StringBuilder. + /// + /// The string builder. + /// The call depth. + public override void ToString(StringBuilder builder, int deep) + { + base.ToString(builder, deep); + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementTextSemanticsQ.cs b/src/WebExpress/WebHtml/HtmlElementTextSemanticsQ.cs new file mode 100644 index 0000000..ccd64f6 --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementTextSemanticsQ.cs @@ -0,0 +1,44 @@ +using System.Collections.Generic; + +namespace WebExpress.WebHtml +{ + /// + /// Bezeichnet ein Kurzzitat. Für längere Zitate sollte
verwendet werden. + ///
+ public class HtmlElementTextSemanticsQ : HtmlElement, IHtmlElementTextSemantics + { + /// + /// Returns the elements. + /// + public new List Elements => base.Elements; + + /// + /// Constructor + /// + public HtmlElementTextSemanticsQ() + : base("q") + { + + } + + /// + /// Constructor + /// + /// 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); + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementTextSemanticsRp.cs b/src/WebExpress/WebHtml/HtmlElementTextSemanticsRp.cs new file mode 100644 index 0000000..28aee5b --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementTextSemanticsRp.cs @@ -0,0 +1,59 @@ +using System.Linq; +using System.Text; + +namespace WebExpress.WebHtml +{ + /// + /// Wird zusammen mit dem Element verwendet, um Ruby-Text mit Klammern zu umgeben, die angezeigt werden, wenn das Benutzerprogramm (Browser) keine Ruby-Annotationen unterstützt. + /// + public class HtmlElementTextSemanticsRp : HtmlElement, IHtmlElementTextSemantics + { + /// + /// Returns or sets the text. + /// + 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)); } + } + + /// + /// Constructor + /// + public HtmlElementTextSemanticsRp() + : base("rp") + { + + } + + /// + /// Constructor + /// + /// The content of the html element. + public HtmlElementTextSemanticsRp(string text) + : this() + { + Text = text; + } + + /// + /// Constructor + /// + /// The content of the html element. + public HtmlElementTextSemanticsRp(params IHtmlNode[] nodes) + : this() + { + Elements.AddRange(nodes); + } + + /// + /// Convert to a string using a StringBuilder. + /// + /// The string builder. + /// The call depth. + public override void ToString(StringBuilder builder, int deep) + { + base.ToString(builder, deep); + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementTextSemanticsRt.cs b/src/WebExpress/WebHtml/HtmlElementTextSemanticsRt.cs new file mode 100644 index 0000000..fea7c14 --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementTextSemanticsRt.cs @@ -0,0 +1,59 @@ +using System.Linq; +using System.Text; + +namespace WebExpress.WebHtml +{ + /// + /// Bezeichnet den Text einer Ruby-Annotation. + /// + public class HtmlElementTextSemanticsRt : HtmlElement, IHtmlElementTextSemantics + { + /// + /// Returns or sets the text. + /// + 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)); } + } + + /// + /// Constructor + /// + public HtmlElementTextSemanticsRt() + : base("rt") + { + + } + + /// + /// Constructor + /// + /// The content of the html element. + public HtmlElementTextSemanticsRt(string text) + : this() + { + Text = text; + } + + /// + /// Constructor + /// + /// The content of the html element. + public HtmlElementTextSemanticsRt(params IHtmlNode[] nodes) + : this() + { + Elements.AddRange(nodes); + } + + /// + /// Convert to a string using a StringBuilder. + /// + /// The string builder. + /// The call depth. + public override void ToString(StringBuilder builder, int deep) + { + base.ToString(builder, deep); + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementTextSemanticsRuby.cs b/src/WebExpress/WebHtml/HtmlElementTextSemanticsRuby.cs new file mode 100644 index 0000000..05e290a --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementTextSemanticsRuby.cs @@ -0,0 +1,44 @@ +using System.Collections.Generic; + +namespace WebExpress.WebHtml +{ + /// + /// Bezeichnet einen Textteil mit Ruby-Annotationen. Dies sind kurze Aussprachetipps und andere Hinweise, die hauptsächlich für ostasiatische Typografie verwendet werden. + /// + public class HtmlElementTextSemanticsRuby : HtmlElement, IHtmlElementTextSemantics + { + /// + /// Returns the elements. + /// + public new List Elements => base.Elements; + + /// + /// Constructor + /// + public HtmlElementTextSemanticsRuby() + : base("ruby") + { + + } + + /// + /// Constructor + /// + /// 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); + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementTextSemanticsS.cs b/src/WebExpress/WebHtml/HtmlElementTextSemanticsS.cs new file mode 100644 index 0000000..ea40c09 --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementTextSemanticsS.cs @@ -0,0 +1,59 @@ +using System.Linq; +using System.Text; + +namespace WebExpress.WebHtml +{ + /// + /// Wird für Inhalte verwendet, dienicht länger relevant oder akkurat sind. Wird meist durchgestrichen dargestellt. + /// + public class HtmlElementTextSemanticsS : HtmlElement, IHtmlElementTextSemantics + { + /// + /// Returns or sets the text. + /// + 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)); } + } + + /// + /// Constructor + /// + public HtmlElementTextSemanticsS() + : base("s") + { + + } + + /// + /// Constructor + /// + /// The content of the html element. + public HtmlElementTextSemanticsS(string text) + : this() + { + Text = text; + } + + /// + /// Constructor + /// + /// The content of the html element. + public HtmlElementTextSemanticsS(params IHtmlNode[] nodes) + : this() + { + Elements.AddRange(nodes); + } + + /// + /// Convert to a string using a StringBuilder. + /// + /// The string builder. + /// The call depth. + public override void ToString(StringBuilder builder, int deep) + { + base.ToString(builder, deep); + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementTextSemanticsSamp.cs b/src/WebExpress/WebHtml/HtmlElementTextSemanticsSamp.cs new file mode 100644 index 0000000..be44723 --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementTextSemanticsSamp.cs @@ -0,0 +1,44 @@ +using System.Collections.Generic; + +namespace WebExpress.WebHtml +{ + /// + /// Markiert die Ausgabe eines Programms oder eines Computers. + /// + public class HtmlElementTextSemanticsSamp : HtmlElement, IHtmlElementTextSemantics + { + /// + /// Returns the elements. + /// + public new List Elements => base.Elements; + + /// + /// Constructor + /// + public HtmlElementTextSemanticsSamp() + : base("samp") + { + + } + + /// + /// Constructor + /// + /// 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); + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementTextSemanticsSmall.cs b/src/WebExpress/WebHtml/HtmlElementTextSemanticsSmall.cs new file mode 100644 index 0000000..611ffb6 --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementTextSemanticsSmall.cs @@ -0,0 +1,59 @@ +using System.Linq; +using System.Text; + +namespace WebExpress.WebHtml +{ + /// + /// Steht für das "Kleingedruckte" eines Dokuments, wie Ausschlussklauseln, Copyright-Hinweise oder andere Dinge, die für das Verständnis des Dokuments nicht unbedingt nötig sind. + /// + public class HtmlElementTextSemanticsSmall : HtmlElement, IHtmlElementTextSemantics + { + /// + /// Returns or sets the text. + /// + 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)); } + } + + /// + /// Constructor + /// + public HtmlElementTextSemanticsSmall() + : base("small") + { + + } + + /// + /// Constructor + /// + /// The content of the html element. + public HtmlElementTextSemanticsSmall(string text) + : this() + { + Text = text; + } + + /// + /// Constructor + /// + /// The content of the html element. + public HtmlElementTextSemanticsSmall(params IHtmlNode[] nodes) + : this() + { + Elements.AddRange(nodes); + } + + /// + /// Convert to a string using a StringBuilder. + /// + /// The string builder. + /// The call depth. + public override void ToString(StringBuilder builder, int deep) + { + base.ToString(builder, deep); + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementTextSemanticsSpan.cs b/src/WebExpress/WebHtml/HtmlElementTextSemanticsSpan.cs new file mode 100644 index 0000000..0a30831 --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementTextSemanticsSpan.cs @@ -0,0 +1,44 @@ +using System.Collections.Generic; + +namespace WebExpress.WebHtml +{ + /// + /// Markiert einen allgemeinen Textabschnitt. + /// + public class HtmlElementTextSemanticsSpan : HtmlElement, IHtmlElementTextSemantics + { + /// + /// Returns the elements. + /// + public new List Elements => base.Elements; + + /// + /// Constructor + /// + public HtmlElementTextSemanticsSpan() + : base("span") + { + + } + + /// + /// Constructor + /// + /// 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); + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementTextSemanticsStrong.cs b/src/WebExpress/WebHtml/HtmlElementTextSemanticsStrong.cs new file mode 100644 index 0000000..8569bb3 --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementTextSemanticsStrong.cs @@ -0,0 +1,59 @@ +using System.Linq; +using System.Text; + +namespace WebExpress.WebHtml +{ + /// + /// Markiert einen besonders wichtigen (stark hervorgehobenen) Text. + /// + public class HtmlElementTextSemanticsStrong : HtmlElement, IHtmlElementTextSemantics + { + /// + /// Returns or sets the text. + /// + 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)); } + } + + /// + /// Constructor + /// + public HtmlElementTextSemanticsStrong() + : base("strong") + { + + } + + /// + /// Constructor + /// + /// The content of the html element. + public HtmlElementTextSemanticsStrong(string text) + : this() + { + Text = text; + } + + /// + /// Constructor + /// + /// The content of the html element. + public HtmlElementTextSemanticsStrong(params IHtmlNode[] nodes) + : this() + { + Elements.AddRange(nodes); + } + + /// + /// Convert to a string using a StringBuilder. + /// + /// The string builder. + /// The call depth. + public override void ToString(StringBuilder builder, int deep) + { + base.ToString(builder, deep); + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementTextSemanticsSub.cs b/src/WebExpress/WebHtml/HtmlElementTextSemanticsSub.cs new file mode 100644 index 0000000..f250e35 --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementTextSemanticsSub.cs @@ -0,0 +1,44 @@ +using System.Collections.Generic; + +namespace WebExpress.WebHtml +{ + /// + /// Markiert einen tiefgestellten Text. + /// + public class HtmlElementTextSemanticsSub : HtmlElement, IHtmlElementTextSemantics + { + /// + /// Returns the elements. + /// + public new List Elements => base.Elements; + + /// + /// Constructor + /// + public HtmlElementTextSemanticsSub() + : base("sub") + { + + } + + /// + /// Constructor + /// + /// 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); + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementTextSemanticsSup.cs b/src/WebExpress/WebHtml/HtmlElementTextSemanticsSup.cs new file mode 100644 index 0000000..635ae26 --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementTextSemanticsSup.cs @@ -0,0 +1,44 @@ +using System.Collections.Generic; + +namespace WebExpress.WebHtml +{ + /// + /// Markiert einen hochgestellten Text. + /// + public class HtmlElementTextSemanticsSup : HtmlElement, IHtmlElementTextSemantics + { + /// + /// Returns the elements. + /// + public new List Elements => base.Elements; + + /// + /// Constructor + /// + public HtmlElementTextSemanticsSup() + : base("sup") + { + + } + + /// + /// Constructor + /// + /// 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); + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementTextSemanticsTime.cs b/src/WebExpress/WebHtml/HtmlElementTextSemanticsTime.cs new file mode 100644 index 0000000..aa32c4d --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementTextSemanticsTime.cs @@ -0,0 +1,49 @@ +using System.Linq; +using System.Text; + +namespace WebExpress.WebHtml +{ + /// + /// Steht für einen Wert, der Datum und Uhrzeit angibt + /// + public class HtmlElementTextSemanticsTime : HtmlElement, IHtmlElementTextSemantics + { + /// + /// Liefert oder setzt das Datum und die Uhrzeit + /// + 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)); } + } + + /// + /// Constructor + /// + public HtmlElementTextSemanticsTime() + : base("time") + { + + } + + /// + /// Constructor + /// + /// The content of the html element. + public HtmlElementTextSemanticsTime(params IHtmlNode[] nodes) + : this() + { + Elements.AddRange(nodes); + } + + /// + /// Convert to a string using a StringBuilder. + /// + /// The string builder. + /// The call depth. + public override void ToString(StringBuilder builder, int deep) + { + base.ToString(builder, deep); + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementTextSemanticsU.cs b/src/WebExpress/WebHtml/HtmlElementTextSemanticsU.cs new file mode 100644 index 0000000..07a7b8a --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementTextSemanticsU.cs @@ -0,0 +1,59 @@ +using System.Linq; +using System.Text; + +namespace WebExpress.WebHtml +{ + /// + /// Steht für einen Textabschnitt, der vom übrigen Inhalt abgesetzt und üblicherweise unterstrichen dargestellt wird, ohne für eine spezielle Betonung oder Wichtigkeit zu stehen. Dies könnte beispielsweise ein Eigenname auf in chinesischer Sprache sein oder ein Textabschnitt, der häufig falsch buchstabiert wird. + /// + public class HtmlElementTextSemanticsU : HtmlElement, IHtmlElementTextSemantics + { + /// + /// Returns or sets the text. + /// + 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)); } + } + + /// + /// Constructor + /// + public HtmlElementTextSemanticsU() + : base("u") + { + + } + + /// + /// Constructor + /// + /// The content of the html element. + public HtmlElementTextSemanticsU(string text) + : this() + { + Text = text; + } + + /// + /// Constructor + /// + /// The content of the html element. + public HtmlElementTextSemanticsU(params IHtmlNode[] nodes) + : this() + { + Elements.AddRange(nodes); + } + + /// + /// Convert to a string using a StringBuilder. + /// + /// The string builder. + /// The call depth. + public override void ToString(StringBuilder builder, int deep) + { + base.ToString(builder, deep); + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementTextSemanticsVar.cs b/src/WebExpress/WebHtml/HtmlElementTextSemanticsVar.cs new file mode 100644 index 0000000..294c309 --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementTextSemanticsVar.cs @@ -0,0 +1,44 @@ +using System.Collections.Generic; + +namespace WebExpress.WebHtml +{ + /// + /// Steht für eine Variable. Dies kann ein tatsächlicher mathematischer Ausdruck oder Programmierungskontext sein, ein Identifier für eine Konstante, ein Symbol für eine physikalische Größe, ein Funktionsparameter oder einfach ein Platzhalter. + /// + public class HtmlElementTextSemanticsVar : HtmlElement, IHtmlElementTextSemantics + { + /// + /// Returns the elements. + /// + public new List Elements => base.Elements; + + /// + /// Constructor + /// + public HtmlElementTextSemanticsVar() + : base("var") + { + + } + + /// + /// Constructor + /// + /// 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); + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementTextSemanticsWbr.cs b/src/WebExpress/WebHtml/HtmlElementTextSemanticsWbr.cs new file mode 100644 index 0000000..7202f89 --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementTextSemanticsWbr.cs @@ -0,0 +1,16 @@ +namespace WebExpress.WebHtml +{ + /// + /// Hiermit kann die Gelegenheit für einen Zeilenumbruch gekennzeichnet werden, mit dem die Lesbarkeit verbessert werden kann, wenn The text. auf mehrere Zeilen verteilt wird. + /// + public class HtmlElementTextSemanticsWbr : HtmlElement, IHtmlElementTextSemantics + { + /// + /// Constructor + /// + public HtmlElementTextSemanticsWbr() + : base("wbr", false) + { + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementWebComponentsSlot.cs b/src/WebExpress/WebHtml/HtmlElementWebComponentsSlot.cs new file mode 100644 index 0000000..87957af --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementWebComponentsSlot.cs @@ -0,0 +1,44 @@ +using System.Collections.Generic; + +namespace WebExpress.WebHtml +{ + /// + /// Kennzeichnet einen Platzhalter + /// + public class HtmlElementWebFragmentsSlot : HtmlElement, IHtmlElementWebFragments + { + /// + /// Returns the elements. + /// + public new List Elements => base.Elements; + + /// + /// Constructor + /// + public HtmlElementWebFragmentsSlot() + : base("slot") + { + + } + + /// + /// Constructor + /// + /// 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); + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlElementWebComponentsTemplate.cs b/src/WebExpress/WebHtml/HtmlElementWebComponentsTemplate.cs new file mode 100644 index 0000000..acbf519 --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlElementWebComponentsTemplate.cs @@ -0,0 +1,44 @@ +using System.Collections.Generic; + +namespace WebExpress.WebHtml +{ + /// + /// Kennzeichnet Html, welches nicht gerendert wird + /// + public class HtmlElementWebFragmentsTemplate : HtmlElement, IHtmlElementWebFragments + { + /// + /// Returns the elements. + /// + public new List Elements => base.Elements; + + /// + /// Constructor + /// + public HtmlElementWebFragmentsTemplate() + : base("template") + { + + } + + /// + /// Constructor + /// + /// 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); + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlEmpty.cs b/src/WebExpress/WebHtml/HtmlEmpty.cs new file mode 100644 index 0000000..f242fe2 --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlEmpty.cs @@ -0,0 +1,30 @@ +using System.Text; + +namespace WebExpress.WebHtml +{ + public class HtmlEmpty : IHtmlNode + { + /// + /// Liefert oder setzt die Text + /// + public string Value { get; set; } + + /// + /// Constructor + /// + public HtmlEmpty() + { + + } + + /// + /// Convert to a string using a StringBuilder. + /// + /// The string builder. + /// The call depth. + public virtual void ToString(StringBuilder builder, int deep) + { + builder.Append(Value); + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlList.cs b/src/WebExpress/WebHtml/HtmlList.cs new file mode 100644 index 0000000..76a35a6 --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlList.cs @@ -0,0 +1,82 @@ +using System.Collections.Generic; +using System.Text; + +namespace WebExpress.WebHtml +{ + /// + /// Liste von HTML-Elementen + /// + public class HtmlList : IHtmlNode + { + /// + /// Returns the elements. + /// + public List Elements { get; private set; } + + /// + /// Constructor + /// + public HtmlList() + { + Elements = new List(); + } + + /// + /// Constructor + /// + /// The content of the html element. + public HtmlList(params IHtmlNode[] nodes) + : this() + { + Elements.AddRange(nodes); + } + + /// + /// Constructor + /// + /// 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); + } + + /// + /// Constructor + /// + /// The content of the html element. + public HtmlList(IEnumerable nodes) + : this() + { + Elements.AddRange(nodes); + } + + /// + /// Constructor + /// + /// 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); + } + + /// + /// Convert to a string using a StringBuilder. + /// + /// The string builder. + /// The call depth. + /// Abschlustag auf neuer Zeile beginnen + public void ToString(StringBuilder builder, int deep) + { + foreach (var v in Elements) + { + v.ToString(builder, deep); + } + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlNbsp.cs b/src/WebExpress/WebHtml/HtmlNbsp.cs new file mode 100644 index 0000000..24b36bd --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlNbsp.cs @@ -0,0 +1,30 @@ +using System.Text; + +namespace WebExpress.WebHtml +{ + public class HtmlNbsp : IHtmlNode + { + /// + /// Liefert oder setzt die Text + /// + public string Value { get; set; } + + /// + /// Constructor + /// + public HtmlNbsp() + { + Value = " "; + } + + /// + /// Convert to a string using a StringBuilder. + /// + /// The string builder. + /// The call depth. + public virtual void ToString(StringBuilder builder, int deep) + { + builder.Append(Value); + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlRaw.cs b/src/WebExpress/WebHtml/HtmlRaw.cs new file mode 100644 index 0000000..d1c848e --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlRaw.cs @@ -0,0 +1,48 @@ +using System.Text; + +namespace WebExpress.WebHtml +{ + public class HtmlRaw : IHtmlNode + { + /// + /// Returns or sets the text. + /// + public string Html { get; set; } + + /// + /// Constructor + /// + public HtmlRaw() + { + + } + + /// + /// Constructor + /// + /// The text. + public HtmlRaw(string html) + { + Html = html; + } + + /// + /// In String konvertieren + /// + /// Das Objekt als String + public override string ToString() + { + return Html; + } + + /// + /// Convert to a string using a StringBuilder. + /// + /// The string builder. + /// The call depth. + public virtual void ToString(StringBuilder builder, int deep) + { + builder.Append(Html); + } + } +} diff --git a/src/WebExpress/WebHtml/HtmlText.cs b/src/WebExpress/WebHtml/HtmlText.cs new file mode 100644 index 0000000..b54b800 --- /dev/null +++ b/src/WebExpress/WebHtml/HtmlText.cs @@ -0,0 +1,39 @@ +using System.Text; + +namespace WebExpress.WebHtml +{ + public class HtmlText : IHtmlNode + { + /// + /// Liefert oder setzt die Text + /// + public string Value { get; set; } + + /// + /// Constructor + /// + public HtmlText() + { + + } + + /// + /// Constructor + /// + /// The text. + public HtmlText(string value) + { + Value = value; + } + + /// + /// Convert to a string using a StringBuilder. + /// + /// The string builder. + /// The call depth. + public virtual void ToString(StringBuilder builder, int deep) + { + builder.Append(Value); + } + } +} diff --git a/src/WebExpress/WebHtml/IHtml.cs b/src/WebExpress/WebHtml/IHtml.cs new file mode 100644 index 0000000..98725c0 --- /dev/null +++ b/src/WebExpress/WebHtml/IHtml.cs @@ -0,0 +1,14 @@ +using System.Text; + +namespace WebExpress.WebHtml +{ + public interface IHtml + { + /// + /// Convert to a string using a StringBuilder. + /// + /// The string builder. + /// The call depth. + void ToString(StringBuilder builder, int deep); + } +} diff --git a/src/WebExpress/WebHtml/IHtmlAttribute.cs b/src/WebExpress/WebHtml/IHtmlAttribute.cs new file mode 100644 index 0000000..ca7785f --- /dev/null +++ b/src/WebExpress/WebHtml/IHtmlAttribute.cs @@ -0,0 +1,11 @@ +namespace WebExpress.WebHtml +{ + public interface IHtmlAttribute : IHtml + { + /// + /// Returns or sets the name. des Attributes + /// + string Name { get; set; } + + } +} diff --git a/src/WebExpress/WebHtml/IHtmlElementEdit.cs b/src/WebExpress/WebHtml/IHtmlElementEdit.cs new file mode 100644 index 0000000..9502033 --- /dev/null +++ b/src/WebExpress/WebHtml/IHtmlElementEdit.cs @@ -0,0 +1,9 @@ +namespace WebExpress.WebHtml +{ + /// + /// Kennzeichnet ein Element welche Textinhalte einer Bedeutung zugewiesen wird + /// + public interface IHtmlElementEdit + { + } +} \ No newline at end of file diff --git a/src/WebExpress/WebHtml/IHtmlElementEmbedded.cs b/src/WebExpress/WebHtml/IHtmlElementEmbedded.cs new file mode 100644 index 0000000..e1c6a5b --- /dev/null +++ b/src/WebExpress/WebHtml/IHtmlElementEmbedded.cs @@ -0,0 +1,9 @@ +namespace WebExpress.WebHtml +{ + /// + /// Kennzeichnet ein Element welche Inhalte einbettet + /// + public interface IHtmlElementEmbedded + { + } +} \ No newline at end of file diff --git a/src/WebExpress/WebHtml/IHtmlElementForm.cs b/src/WebExpress/WebHtml/IHtmlElementForm.cs new file mode 100644 index 0000000..4d13167 --- /dev/null +++ b/src/WebExpress/WebHtml/IHtmlElementForm.cs @@ -0,0 +1,9 @@ +namespace WebExpress.WebHtml +{ + /// + /// Kennzeichnet ein Element welche Teil eines Formulars ist + /// + public interface IHtmlElementForm + { + } +} \ No newline at end of file diff --git a/src/WebExpress/WebHtml/IHtmlElementInteractive.cs b/src/WebExpress/WebHtml/IHtmlElementInteractive.cs new file mode 100644 index 0000000..d419b65 --- /dev/null +++ b/src/WebExpress/WebHtml/IHtmlElementInteractive.cs @@ -0,0 +1,9 @@ +namespace WebExpress.WebHtml +{ + /// + /// Kennzeichnet ein Element welches interaktiv ist + /// + public interface IHtmlElementInteractive + { + } +} \ No newline at end of file diff --git a/src/WebExpress/WebHtml/IHtmlElementMetadata.cs b/src/WebExpress/WebHtml/IHtmlElementMetadata.cs new file mode 100644 index 0000000..1a6ee25 --- /dev/null +++ b/src/WebExpress/WebHtml/IHtmlElementMetadata.cs @@ -0,0 +1,9 @@ +namespace WebExpress.WebHtml +{ + /// + /// Kennzeichnet ein Element als Metadaten + /// + public interface IHtmlElementMetadata + { + } +} \ No newline at end of file diff --git a/src/WebExpress/WebHtml/IHtmlElementMultimedia.cs b/src/WebExpress/WebHtml/IHtmlElementMultimedia.cs new file mode 100644 index 0000000..ec567ff --- /dev/null +++ b/src/WebExpress/WebHtml/IHtmlElementMultimedia.cs @@ -0,0 +1,9 @@ +namespace WebExpress.WebHtml +{ + /// + /// Kennzeichnet ein Element welche Multimedia-Inhalte besitzt + /// + public interface IHtmlElementMultimedia + { + } +} \ No newline at end of file diff --git a/src/WebExpress/WebHtml/IHtmlElementRoot.cs b/src/WebExpress/WebHtml/IHtmlElementRoot.cs new file mode 100644 index 0000000..bfd0ee0 --- /dev/null +++ b/src/WebExpress/WebHtml/IHtmlElementRoot.cs @@ -0,0 +1,9 @@ +namespace WebExpress.WebHtml +{ + /// + /// Kennzeichnet ein Element als Wurzel + /// + public interface IHtmlElementRoot + { + } +} \ No newline at end of file diff --git a/src/WebExpress/WebHtml/IHtmlElementScripting.cs b/src/WebExpress/WebHtml/IHtmlElementScripting.cs new file mode 100644 index 0000000..a9a55b0 --- /dev/null +++ b/src/WebExpress/WebHtml/IHtmlElementScripting.cs @@ -0,0 +1,9 @@ +namespace WebExpress.WebHtml +{ + /// + /// Kennzeichnet ein Element als Skript + /// + public interface IHtmlElementScripting + { + } +} \ No newline at end of file diff --git a/src/WebExpress/WebHtml/IHtmlElementSection.cs b/src/WebExpress/WebHtml/IHtmlElementSection.cs new file mode 100644 index 0000000..d9b0067 --- /dev/null +++ b/src/WebExpress/WebHtml/IHtmlElementSection.cs @@ -0,0 +1,9 @@ +namespace WebExpress.WebHtml +{ + /// + /// Kennzeichnet ein Element als Abschnitt + /// + public interface IHtmlElementSection + { + } +} \ No newline at end of file diff --git a/src/WebExpress/WebHtml/IHtmlElementTable.cs b/src/WebExpress/WebHtml/IHtmlElementTable.cs new file mode 100644 index 0000000..0baa207 --- /dev/null +++ b/src/WebExpress/WebHtml/IHtmlElementTable.cs @@ -0,0 +1,9 @@ +namespace WebExpress.WebHtml +{ + /// + /// Kennzeichnet ein Element welche Teil einer Tabelle ist + /// + public interface IHtmlElementTable + { + } +} \ No newline at end of file diff --git a/src/WebExpress/WebHtml/IHtmlElementTextContent.cs b/src/WebExpress/WebHtml/IHtmlElementTextContent.cs new file mode 100644 index 0000000..a5db9b6 --- /dev/null +++ b/src/WebExpress/WebHtml/IHtmlElementTextContent.cs @@ -0,0 +1,9 @@ +namespace WebExpress.WebHtml +{ + /// + /// Kennzeichnet ein Element welche Textinhalt strukturiert + /// + public interface IHtmlElementTextContent + { + } +} \ No newline at end of file diff --git a/src/WebExpress/WebHtml/IHtmlElementTextSemantics.cs b/src/WebExpress/WebHtml/IHtmlElementTextSemantics.cs new file mode 100644 index 0000000..b8b10d6 --- /dev/null +++ b/src/WebExpress/WebHtml/IHtmlElementTextSemantics.cs @@ -0,0 +1,9 @@ +namespace WebExpress.WebHtml +{ + /// + /// Kennzeichnet ein Element welche Textinhalte einer Bedeutung zugewiesen wird + /// + public interface IHtmlElementTextSemantics + { + } +} \ No newline at end of file diff --git a/src/WebExpress/WebHtml/IHtmlElementWebComponents.cs b/src/WebExpress/WebHtml/IHtmlElementWebComponents.cs new file mode 100644 index 0000000..4938002 --- /dev/null +++ b/src/WebExpress/WebHtml/IHtmlElementWebComponents.cs @@ -0,0 +1,9 @@ +namespace WebExpress.WebHtml +{ + /// + /// Kennzeichnet ein Element welche als Platzhalter dient + /// + public interface IHtmlElementWebFragments + { + } +} \ No newline at end of file diff --git a/src/WebExpress/WebHtml/IHtmlFormularItem.cs b/src/WebExpress/WebHtml/IHtmlFormularItem.cs new file mode 100644 index 0000000..86b3793 --- /dev/null +++ b/src/WebExpress/WebHtml/IHtmlFormularItem.cs @@ -0,0 +1,6 @@ +namespace WebExpress.WebHtml +{ + public interface IHtmlFormularItem : IHtmlElementForm + { + } +} diff --git a/src/WebExpress/WebHtml/IHtmlNode.cs b/src/WebExpress/WebHtml/IHtmlNode.cs new file mode 100644 index 0000000..608e9c5 --- /dev/null +++ b/src/WebExpress/WebHtml/IHtmlNode.cs @@ -0,0 +1,6 @@ +namespace WebExpress.WebHtml +{ + public interface IHtmlNode : IHtml + { + } +} diff --git a/src/WebExpress/WebHtml/Style.cs b/src/WebExpress/WebHtml/Style.cs new file mode 100644 index 0000000..2ad6626 --- /dev/null +++ b/src/WebExpress/WebHtml/Style.cs @@ -0,0 +1,28 @@ +using System.Linq; + +namespace WebExpress.WebHtml +{ + public static class Style + { + /// + /// Verbindet die angebenen Styles zu einem String + /// + /// Die einzelnen Styles + /// Die Styles als String + public static string Concatenate(params string[] items) + { + return string.Join(" ", items.Where(x => !string.IsNullOrWhiteSpace(x)).Distinct()); + } + + /// + /// Entfernt die angegebenen Styles aus dem Gesammtsring + /// + /// Die in einem gemeinsamen String verbundenen Styles + /// Die zu entfernenden Styles + /// Die Styles als String + public static string Remove(string styles, params string[] remove) + { + return string.Join(" ", styles.Split(' ').Where(x => !remove.Contains(x))); + } + } +} diff --git a/src/WebExpress/WebHtml/TypeEnctype.cs b/src/WebExpress/WebHtml/TypeEnctype.cs new file mode 100644 index 0000000..f127008 --- /dev/null +++ b/src/WebExpress/WebHtml/TypeEnctype.cs @@ -0,0 +1,60 @@ +namespace WebExpress.WebHtml +{ + /// + /// Legt fest, wie die Daten encoded werden, wenn sie zum Server übertragen werden. + /// + public enum TypeEnctype + { + /// + /// Alle Zeichen werden encoded (Spaces wird zu "+" koncertiert und spezalzeichen in der Hexrepräsentation) + /// + UrLEncoded, + /// + /// Keine Zeichen werden encodes. Wird verwendet, wenn Dateien übertragen werden + /// + None, + /// + /// Nur Space-Zeichen werden encodiert. + /// + Text, + /// + /// Nicht zuordbar + /// + Default + } + + public static class TypeEnctypeExtensions + { + /// + /// Umwandlung von String in TypeEnctype + /// + /// Die Kodierung + /// Die umgewandelte Kodierung + public static TypeEnctype Convert(string enctype) + { + return (enctype?.ToLower()) switch + { + "multipart/form-data" => TypeEnctype.None, + "text/plain" => TypeEnctype.Text, + "application/x-www-form-urlencoded" => TypeEnctype.UrLEncoded, + _ => TypeEnctype.Default, + }; + } + + + /// + /// Conversion to string.repräsentation + /// + /// Die Kodierung + /// Die umgewandelte Kodierung + public static string Convert(this TypeEnctype enctype) + { + return enctype switch + { + TypeEnctype.None => "multipart/form-data", + TypeEnctype.Text => "text/plain", + _ => "application/x-www-form-urlencoded", + }; + } + } +} diff --git a/src/WebExpress/WebHtml/TypeFavicon.cs b/src/WebExpress/WebHtml/TypeFavicon.cs new file mode 100644 index 0000000..15b4eb9 --- /dev/null +++ b/src/WebExpress/WebHtml/TypeFavicon.cs @@ -0,0 +1,11 @@ +namespace WebExpress.WebHtml +{ + public enum TypeFavicon + { + Default, + ICON, + PNG, + JPG, + SVG + } +} diff --git a/src/WebExpress/WebHtml/TypeTarget.cs b/src/WebExpress/WebHtml/TypeTarget.cs new file mode 100644 index 0000000..ea8bc3f --- /dev/null +++ b/src/WebExpress/WebHtml/TypeTarget.cs @@ -0,0 +1,33 @@ +namespace WebExpress.WebHtml +{ + public enum TypeTarget + { + None, + Blank, + Self, + Parent, + Top, + Framename + } + + public static class TypeTargetExtensions + { + /// + /// Umwandlung in einen Klartext + /// + /// Das Aufrufsziel + /// Der Klartext des Targets + public static string ToStringValue(this TypeTarget target) + { + return target switch + { + TypeTarget.Blank => "_blank", + TypeTarget.Self => "_self", + TypeTarget.Parent => "_parent", + TypeTarget.Top => "_top", + TypeTarget.Framename => "_framename", + _ => string.Empty, + }; + } + } +} diff --git a/src/WebExpress/WebJob/Clock.cs b/src/WebExpress/WebJob/Clock.cs new file mode 100644 index 0000000..3343e66 --- /dev/null +++ b/src/WebExpress/WebJob/Clock.cs @@ -0,0 +1,198 @@ +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("WebExpress.Test")] + +namespace WebExpress.WebJob +{ + public class Clock + { + /// + /// The underlying date and time. + /// + private DateTime DateTime { get; set; } + + /// + /// The minute 0-59. + /// + public int Minute => DateTime.Minute; + + /// + /// The hour 0-23. + /// + public int Hour => DateTime.Hour; + + /// + /// The day 1-31. + /// + public int Day => DateTime.Day; + + /// + /// The month 1-12. + /// + public int Month => DateTime.Month; + + /// + /// The weekday 0-6 (Sunday-Saturday). + /// + public int Weekday => (int)DateTime.DayOfWeek; + + /// + /// Constructor + /// + public Clock() + { + var dateTime = DateTime.Now; + + DateTime = new DateTime(dateTime.Year, dateTime.Month, dateTime.Day, dateTime.Hour, dateTime.Minute, 0); + } + + /// + /// Constructor + /// + /// The time to copy. + public Clock(DateTime dateTime) + { + DateTime = new DateTime(dateTime.Year, dateTime.Month, dateTime.Day, dateTime.Hour, dateTime.Minute, 0); + } + + /// + /// Copy-Constructor + /// + /// The clock to be copied. + public Clock(Clock clock) + { + DateTime = clock.DateTime; + } + + /// + /// Move the clock one tick (one minute) further. + /// + internal void Tick() + { + DateTime = DateTime.AddMinutes(1); + } + + /// + /// Synchronizes the clock with the current time. + /// + /// The missed times. + public IEnumerable Synchronize() + { + var dateTime = DateTime.Now; + var current = new Clock(); + var next = new Clock(this); + + var elapsed = new List(); + + while (next < current) + { + elapsed.Add(new Clock(next)); + next.Tick(); + } + + DateTime = new DateTime(dateTime.Year, dateTime.Month, dateTime.Day, dateTime.Hour, dateTime.Minute, 0); + + return elapsed; + } + + /// + /// Compare two times. + /// + /// The first time. + /// The second time. + /// True if both times match, false otherwise. + public static bool operator ==(Clock obj1, Clock obj2) + { + return obj1.Minute == obj2.Minute && obj1.Hour == obj2.Hour && obj1.Day == obj2.Day && obj1.Month == obj2.Month; + } + + /// + /// Compares two times for inequality + /// + /// The first time. + /// The second time. + /// True if both times do not match, false otherwise. + public static bool operator !=(Clock obj1, Clock obj2) + { + return obj1.Minute != obj2.Minute || obj1.Hour != obj2.Hour || obj1.Day != obj2.Day || obj1.Month != obj2.Month; + } + + /// + /// Compares two times to less than. + /// + /// The first time. + /// The second time. + /// 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; + } + + /// + /// Compares two times to greater than. + /// + /// The first time. + /// The second time. + /// 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; + } + + /// + /// Compares two times to less than or equal to. + /// + /// The first time. + /// The second time. + /// True if the time on the left is less than the time on the right, otherwise it is false. + public static bool operator <=(Clock obj1, Clock obj2) + { + return obj1 < obj2 || obj1 == obj2; + } + + /// + /// Compares two times to greater than or equal to. + /// + /// The first time. + /// The second time. + /// 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 > obj2 || obj1 == obj2; + } + + /// + /// The comparison function. + /// + /// The time to compare. + /// True if both times are the same, false otherwise. + public override bool Equals(object obj) + { + if (obj is null) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + return Minute == (obj as Clock).Minute && + Hour == (obj as Clock).Hour && + Day == (obj as Clock).Day && + Month == (obj as Clock).Month; + } + + /// + /// Returns the hash code for this instance. + /// + /// The hash code. + public override int GetHashCode() + { + return DateTime.GetHashCode(); + } + } +} diff --git a/src/WebExpress/WebJob/Cron.cs b/src/WebExpress/WebJob/Cron.cs new file mode 100644 index 0000000..0ed4e4c --- /dev/null +++ b/src/WebExpress/WebJob/Cron.cs @@ -0,0 +1,155 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using static WebExpress.Internationalization.InternationalizationManager; + +namespace WebExpress.WebJob +{ + /// + /// Manages commands that are executed on a scheduled basis (CRON = command run on notice). + /// + /// + public class Cron + { + /// + /// Returns or sets the reference to the context of the host + /// + private static IHttpServerContext Context { get; set; } + + /// + /// The minute 0-59 or * for any. Comma seperated values or ranges (-) are also possible. + /// + private List Minute { get; } = new List(); + + /// + /// The hour 0-23 or * for any. Comma seperated values or ranges (-) are also possible. + /// + private List Hour { get; } = new List(); + + /// + /// The day 1-31 or * for any. Comma seperated values or ranges (-) are also possible. + /// + private List Day { get; } = new List(); + + /// + /// The month 1-12 or * for any. Comma seperated values or ranges (-) are also possible. + /// + private List Month { get; } = new List(); + + /// + /// 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(); + + /// + /// Constructor + /// + /// The reference to the host's context. + /// The minute 0-59 or * for any. Comma seperated values or ranges (-) are also possible. + /// The hour 0-23 or * for any. Comma seperated values or ranges (-) are also possible. + /// The day 1-31 or * for any. Comma seperated values or ranges (-) are also possible. + /// The month 1-12 or * for any. Comma seperated values or ranges (-) are also possible. + /// The day of the week 0-6 (Sunday-Saturday) or * for any. Comma seperated values or ranges (-) are also possible. + public Cron(IHttpServerContext context, string minute = "*", string hour = "*", string day = "*", string month = "*", string weekday = "*") + { + 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)); + } + + /// + /// Parses the value. + /// + /// The value to parse. + /// The minimum. + /// The maximum. + /// The parsed values. + private IEnumerable Parse(string value, int minValue, int maxValue) + { + var items = new List() as IEnumerable; + value = value?.ToLower().Trim(); + + if (string.IsNullOrEmpty(value) || value.Equals("*")) + { + return Enumerable.Range(minValue, maxValue).ToList(); + } + + var commaSeparatedList = value.Split + ( + ',', + System.StringSplitOptions.RemoveEmptyEntries | System.StringSplitOptions.TrimEntries + ); + + foreach (var i in commaSeparatedList) + { + var range = i.Split('-', System.StringSplitOptions.RemoveEmptyEntries | System.StringSplitOptions.TrimEntries); + if (range.Length == 2) + { + // range + var min = int.MinValue; + var max = int.MinValue; + + if (int.TryParse(range[0], out int minResult)) + { + min = Math.Max(minResult, minValue); + } + + if (int.TryParse(range[1], out int maxResult)) + { + max = Math.Min(maxResult, maxValue); + } + + if (min != int.MinValue && max != int.MinValue && min < max) + { + items = items.Union(Enumerable.Range(min, (max - min) + 1)); + } + else + { + Context.Log.Warning(message: I18N("webexpress:schedulermanager.cron.range"), args: value); + } + } + else if (range.Length == 1) + { + if (int.TryParse(range[0], out int result)) + { + if (result >= minValue && result <= maxValue) + { + items = items.Union(new List { result }); + } + else + { + Context.Log.Warning(message: I18N("webexpress:schedulermanager.cron.range"), args: result); + } + } + else + { + Context.Log.Warning(message: I18N("webexpress:schedulermanager.cron.parseerror"), args: value); + } + } + else + { + Context.Log.Warning(message: I18N("webexpress:schedulermanager.cron.parseerror"), args: value); + } + } + + return items; + } + + /// + /// Checks whether the cyclic processing of a command can take place. + /// + /// True if there is a match, false otherwise. + public bool Matching(Clock clock) + { + return Minute.Contains(clock.Minute) && + Hour.Contains(clock.Hour) && + Day.Contains(clock.Day) && + Month.Contains(clock.Month) && + Weekday.Contains(clock.Weekday); + } + } +} diff --git a/src/WebExpress/WebJob/IJob.cs b/src/WebExpress/WebJob/IJob.cs new file mode 100644 index 0000000..2a62c98 --- /dev/null +++ b/src/WebExpress/WebJob/IJob.cs @@ -0,0 +1,22 @@ +using WebExpress.Internationalization; + +namespace WebExpress.WebJob +{ + /// + /// A task that can be performed cyclically. + /// + public interface IJob : II18N + { + /// + /// Initialization + /// + /// The context in which the job is executed. + public void Initialization(IJobContext context); + + /// + /// Processing of the resource. + /// + public void Process(); + + } +} diff --git a/src/WebExpress/WebJob/IJobContext.cs b/src/WebExpress/WebJob/IJobContext.cs new file mode 100644 index 0000000..1a612b8 --- /dev/null +++ b/src/WebExpress/WebJob/IJobContext.cs @@ -0,0 +1,28 @@ +using WebExpress.WebModule; +using WebExpress.WebPlugin; + +namespace WebExpress.WebJob +{ + public interface IJobContext + { + /// + /// Returns the associated plugin context. + /// + IPluginContext PluginContext { get; } + + /// + /// Returns the corresponding module context. + /// + IModuleContext ModuleContext { get; } + + /// + /// Returns the job id. + /// + string JobId { get; } + + /// + /// Returns the cron-object. + /// + Cron Cron { get; } + } +} diff --git a/src/WebExpress/WebJob/Job.cs b/src/WebExpress/WebJob/Job.cs new file mode 100644 index 0000000..b3ef4dd --- /dev/null +++ b/src/WebExpress/WebJob/Job.cs @@ -0,0 +1,38 @@ + +using System.Globalization; + +namespace WebExpress.WebJob +{ + /// + /// A task that can be performed cyclically. + /// + public class Job : IJob + { + /// + /// Returns or sets the culture. + /// + public CultureInfo Culture { get; set; } + + /// + /// Returns the context. + /// + public IJobContext Context { get; private set; } + + /// + /// Initialization + /// + /// The context in which the job is executed. + public virtual void Initialization(IJobContext context) + { + Context = context; + } + + /// + /// Processing of the resource. + /// + public virtual void Process() + { + + } + } +} diff --git a/src/WebExpress/WebJob/JobContext.cs b/src/WebExpress/WebJob/JobContext.cs new file mode 100644 index 0000000..d358f4a --- /dev/null +++ b/src/WebExpress/WebJob/JobContext.cs @@ -0,0 +1,47 @@ +using WebExpress.WebModule; +using WebExpress.WebPlugin; + +namespace WebExpress.WebJob +{ + public class JobContext : IJobContext + { + /// + /// 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; } + + /// + /// 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/WebJob/JobManager.cs b/src/WebExpress/WebJob/JobManager.cs new file mode 100644 index 0000000..86e3bb5 --- /dev/null +++ b/src/WebExpress/WebJob/JobManager.cs @@ -0,0 +1,431 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using WebExpress.Internationalization; +using WebExpress.WebAttribute; +using WebExpress.WebComponent; +using WebExpress.WebModule; +using WebExpress.WebPlugin; + +namespace WebExpress.WebJob +{ + /// + /// Processing of cyclic jobs + /// + public sealed class JobManager : IComponentPlugin, ISystemComponent, IExecutableElements + { + /// + /// Thread termination. + /// + private CancellationTokenSource TokenSource { get; } = new CancellationTokenSource(); + + /// + /// The clock for determining the execution of the crons. + /// + private Clock Clock { get; } = new Clock(); + + /// + /// Returns or sets the reference to the context of the host. + /// + public IHttpServerContext HttpServerContext { get; private set; } + + /// + /// Returns the directory where the static jobs are listed. + /// + private ScheduleDictionary StaticScheduleDictionary { get; } = new ScheduleDictionary(); + + /// + /// Returns the directory where the dynamic jobs are listed. + /// + private IEnumerable DynamicScheduleList { get; set; } = new List(); + + /// + /// Constructor + /// + internal JobManager() + { + ComponentManager.PluginManager.AddPlugin += (sender, pluginContext) => + { + Register(pluginContext); + }; + + ComponentManager.PluginManager.RemovePlugin += (sender, pluginContext) => + { + Remove(pluginContext); + }; + + ComponentManager.ModuleManager.AddModule += (sender, moduleContext) => + { + AssignToModule(moduleContext); + }; + + ComponentManager.ModuleManager.RemoveModule += (sender, moduleContext) => + { + DetachFromModule(moduleContext); + }; + } + + /// + /// Initialization + /// + /// The reference to the context of the host. + public void Initialization(IHttpServerContext context) + { + HttpServerContext = context; + + HttpServerContext.Log.Debug + ( + InternationalizationManager.I18N + ( + "webexpress:jobmanager.initialization" + ) + ); + } + + /// + /// Discovers and registers jobs from the specified plugin. + /// + /// A context of a plugin whose jobs are to be registered. + public void Register(IPluginContext pluginContext) + { + var assembly = pluginContext?.Assembly; + + foreach (var job in assembly.GetTypes().Where + ( + x => x.IsClass == true && + x.IsSealed && + x.IsPublic && + x.GetInterface(typeof(IJob).Name) != null + )) + { + 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))) + { + minute = customAttribute.ConstructorArguments.FirstOrDefault().Value?.ToString(); + hour = customAttribute.ConstructorArguments.Skip(1).FirstOrDefault().Value?.ToString(); + day = customAttribute.ConstructorArguments.Skip(2).FirstOrDefault().Value?.ToString(); + month = customAttribute.ConstructorArguments.Skip(3).FirstOrDefault().Value?.ToString(); + weekday = customAttribute.ConstructorArguments.Skip(4).FirstOrDefault().Value?.ToString(); + } + + foreach (var customAttribute in job.CustomAttributes + .Where(x => x.AttributeType.GetInterfaces().Contains(typeof(IModuleAttribute)))) + { + if (customAttribute.AttributeType.Name == typeof(ModuleAttribute<>).Name && customAttribute.AttributeType.Namespace == typeof(ModuleAttribute<>).Namespace) + { + moduleId = customAttribute.AttributeType.GenericTypeArguments.FirstOrDefault()?.FullName?.ToLower(); + } + } + + if (string.IsNullOrWhiteSpace(moduleId)) + { + // no module specified + HttpServerContext.Log.Warning + ( + InternationalizationManager.I18N + ( + "webexpress:jobmanager.moduleless", id + ) + ); + } + + // register the job + if (!StaticScheduleDictionary.ContainsKey(pluginContext)) + { + StaticScheduleDictionary.Add(pluginContext, new List()); + } + + var dictItem = StaticScheduleDictionary[pluginContext]; + + 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) + { + // job is not part of the module + HttpServerContext.Log.Warning + ( + InternationalizationManager.I18N + ( + "webexpress:jobmanager.wrongmodule", + moduleContext.ModuleId, id + ) + ); + } + + AssignToModule(moduleContext); + } + } + } + + /// + /// Discovers and registers entries from the specified plugin. + /// + /// A list with plugin contexts that contain the jobs. + public void Register(IEnumerable pluginContexts) + { + foreach (var pluginContext in pluginContexts) + { + Register(pluginContext); + } + } + + /// + /// Registers a job. + /// + /// The plugin context. + /// The cropn-object. + /// The job. + public IJob Register(IPluginContext pluginContext, Cron cron) where T : IJob + { + // create context + var jobContext = new JobContext(pluginContext) + { + 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); + + return jobInstance; + } + + /// + /// Registers a job. + /// + /// The module context. + /// The cropn-object. + /// The job. + public IJob Register(IModuleContext moduleContext, Cron cron) where T : IJob + { + // 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); + + return jobInstance; + } + + /// + /// Assign existing job to the module. + /// + /// The context of the module. + private void AssignToModule(IModuleContext moduleContext) + { + foreach (var scheduleItem in StaticScheduleDictionary.Values.SelectMany(x => x)) + { + if (scheduleItem.moduleId.Equals(moduleContext?.ModuleId)) + { + scheduleItem.AddModule(moduleContext); + } + } + } + + /// + /// Remove an existing modules to the job. + /// + /// The context of the module. + private void DetachFromModule(IModuleContext moduleContext) + { + foreach (var scheduleItem in StaticScheduleDictionary.Values.SelectMany(x => x)) + { + if (scheduleItem.moduleId.Equals(moduleContext?.ModuleId)) + { + scheduleItem.DetachModule(moduleContext); + } + } + } + + /// + /// Retruns the schedule item for a given plugin. + /// + /// The context of the plugin. + /// An enumeration of the schedule item for the given plugin. + internal IEnumerable GetScheduleItems(IPluginContext pluginContext) + { + if (pluginContext == null || !StaticScheduleDictionary.ContainsKey(pluginContext)) + { + return Enumerable.Empty(); + } + + return StaticScheduleDictionary[pluginContext]; + } + + /// + /// Executes the schedule. + /// + internal void Execute() + { + Task.Factory.StartNew(() => + { + while (!TokenSource.IsCancellationRequested) + { + Update(); + + var secendsLeft = 60 - DateTime.Now.Second; + Thread.Sleep(secendsLeft * 1000); + } + + }, TokenSource.Token); + } + + /// + /// Run jobs on demand (concurrent execution). + /// + private void Update() + { + foreach (var clock in Clock.Synchronize()) + { + foreach (var scheduleItemValue in StaticScheduleDictionary.Values + .SelectMany(x => x) + .SelectMany(x => x.Dictionary.Values)) + { + if (scheduleItemValue.JobContext.Cron.Matching(Clock)) + { + HttpServerContext.Log.Debug + ( + InternationalizationManager.I18N + ( + "webexpress:jobmanager.job.process", + scheduleItemValue.JobContext.JobId + ) + ); + + Task.Factory.StartNew(() => + { + scheduleItemValue.Instance?.Process(); + }, TokenSource.Token); + } + } + + foreach (var scheduleItemValue in DynamicScheduleList) + { + if (scheduleItemValue.JobContext.Cron.Matching(Clock)) + { + HttpServerContext.Log.Debug + ( + InternationalizationManager.I18N + ( + "webexpress:jobmanager.job.process", + scheduleItemValue.JobContext.JobId + ) + ); + + Task.Factory.StartNew(() => + { + scheduleItemValue.Instance?.Process(); + }, TokenSource.Token); + } + } + } + } + + /// + /// Stop running the scheduler. + /// + public void ShutDown() + { + TokenSource.Cancel(); + } + + /// + /// Removes all jobs associated with the specified plugin context. + /// + /// The context of the plugin that contains the jobs to remove. + public void Remove(IPluginContext pluginContext) + { + // the plugin has not been registered in the manager + if (!StaticScheduleDictionary.ContainsKey(pluginContext)) + { + return; + } + + 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 + ) + ); + } + } + } +} diff --git a/src/WebExpress/WebJob/ScheduleDictionary.cs b/src/WebExpress/WebJob/ScheduleDictionary.cs new file mode 100644 index 0000000..32a795e --- /dev/null +++ b/src/WebExpress/WebJob/ScheduleDictionary.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; +using WebExpress.WebPlugin; + +namespace WebExpress.WebJob +{ + /// + /// key = plugin context + /// value = ressource items + /// + internal class ScheduleDictionary : Dictionary> + { + } +} diff --git a/src/WebExpress/WebJob/ScheduleIDynamicItem.cs b/src/WebExpress/WebJob/ScheduleIDynamicItem.cs new file mode 100644 index 0000000..ac53de3 --- /dev/null +++ b/src/WebExpress/WebJob/ScheduleIDynamicItem.cs @@ -0,0 +1,25 @@ +using System.Threading; + +namespace WebExpress.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/WebJob/ScheduleStaticItem.cs b/src/WebExpress/WebJob/ScheduleStaticItem.cs new file mode 100644 index 0000000..fb179d3 --- /dev/null +++ b/src/WebExpress/WebJob/ScheduleStaticItem.cs @@ -0,0 +1,180 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using WebExpress.WebModule; +using WebExpress.WebPlugin; + +namespace WebExpress.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/WebJob/ScheduleStaticItemValue.cs b/src/WebExpress/WebJob/ScheduleStaticItemValue.cs new file mode 100644 index 0000000..19103cd --- /dev/null +++ b/src/WebExpress/WebJob/ScheduleStaticItemValue.cs @@ -0,0 +1,25 @@ +using System.Threading; + +namespace WebExpress.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/WebMessage/HttpContext.cs b/src/WebExpress/WebMessage/HttpContext.cs new file mode 100644 index 0000000..1b3e2d3 --- /dev/null +++ b/src/WebExpress/WebMessage/HttpContext.cs @@ -0,0 +1,82 @@ +using Microsoft.AspNetCore.Http.Features; +using System; +using System.Linq; +using System.Net; +using System.Text; + +namespace WebExpress.WebMessage +{ + public class HttpContext + { + /// + /// The context of the web server. + /// + public IHttpServerContext ServerContext { get; protected set; } + + /// + /// Returns or sets the id. + /// + public string Id { get; protected set; } + + /// + /// Returns the request. + /// + public Request Request { get; protected set; } + + /// + /// Gets the ip address and port number of the server to which the request is made. + /// + public EndPoint LocalEndPoint { get; protected set; } + + /// + /// Gets the ip address and port number of the client from which the request originated. + /// + public EndPoint RemoteEndPoint { get; protected set; } + + /// + /// Set of features. + /// + public IFeatureCollection Features { get; protected set; } + + /// + /// The encoding. + /// + public Encoding Encoding { get; protected set; } = Encoding.Default; + + /// + /// Returns the uri. + /// + public Uri Uri { get; internal set; } + + /// + /// Constructor + /// + internal HttpContext() + { + + } + + /// + /// Constructor + /// + /// Initial set of features. + /// The context of the Web server. + public HttpContext(IFeatureCollection contextFeatures, IHttpServerContext serverContext) + { + var connectionFeature = contextFeatures.Get(); + var requestFeature = contextFeatures.Get(); + var header = new RequestHeaderFields(contextFeatures); + var baseUri = new UriBuilder(requestFeature.Scheme, header.Host, connectionFeature.LocalPort).Uri; + + Features = contextFeatures; + Id = connectionFeature.ConnectionId; + 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; + Uri = new Uri(baseUri, requestFeature.RawTarget); + + Request = new Request(contextFeatures, serverContext, header); + } + } +} diff --git a/src/WebExpress/WebMessage/HttpExceptionContext.cs b/src/WebExpress/WebMessage/HttpExceptionContext.cs new file mode 100644 index 0000000..fd9e26b --- /dev/null +++ b/src/WebExpress/WebMessage/HttpExceptionContext.cs @@ -0,0 +1,33 @@ +using Microsoft.AspNetCore.Http.Features; +using System; +using System.Net; + +namespace WebExpress.WebMessage +{ + public class HttpExceptionContext : HttpContext + { + /// + /// Returns or sets an error message if the context could not be created. + /// + public Exception Exception { get; private set; } + + /// + /// Constructor + /// + /// 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(); + + Features = contextFeatures; + Id = connectionFeature.ConnectionId; + + LocalEndPoint = new IPEndPoint(connectionFeature.LocalIpAddress, connectionFeature.LocalPort); + RemoteEndPoint = new IPEndPoint(connectionFeature.RemoteIpAddress, connectionFeature.RemotePort); + + Exception = exception; + } + } +} diff --git a/src/WebExpress/WebMessage/Parameter.cs b/src/WebExpress/WebMessage/Parameter.cs new file mode 100644 index 0000000..28d0730 --- /dev/null +++ b/src/WebExpress/WebMessage/Parameter.cs @@ -0,0 +1,137 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace WebExpress.WebMessage +{ + public class Parameter + { + /// + /// Returns or sets the scope of the parameter. + /// + public ParameterScope Scope { get; private set; } + + /// + /// The key. + /// + public string Key { get; private set; } + + /// + /// The value. + /// + public string Value { get; internal set; } + + /// + /// Constructor + /// + public Parameter() + { + } + + /// + /// Constructor + /// + /// The key. + /// The value. + /// The scope of the parameter. + public Parameter(string key, string value, ParameterScope scope) + { + Key = key.ToLower(); + Value = value; + Scope = scope; + + if (scope == ParameterScope.Parameter) + { + var decode = System.Web.HttpUtility.UrlDecode(value); + Value = decode; + } + } + + /// + /// Constructor + /// + /// The key. + /// The value. + /// The scope of the parameter. + public Parameter(string key, int value, ParameterScope scope) + { + Key = key.ToLower(); + Value = value.ToString(); + Scope = scope; + } + + /// + /// Constructor + /// + /// The key. + /// The value. + /// The scope of the parameter. + public Parameter(string key, char value, ParameterScope scope) + { + Key = key.ToLower(); + Value = value.ToString(); + Scope = scope; + } + + /// + /// Creates a parameter list. + /// + /// The elements of the parameter list. + /// The parameter list. + public static List Create(params Parameter[] param) + { + return new List(param); + } + + /// + /// Returns the key. + /// + /// The type. + /// The key. + public static string GetKey() where T : Parameter + { + return (Activator.CreateInstance(typeof(T)) as T)?.Key; + } + + /// + /// Conversion to string form. + /// + /// The object in its string representation. + public override string ToString() + { + var sb = new StringBuilder(Value); + + sb.Replace("%", "%25"); // Attention! & must come first + sb.Replace(" ", "%20"); + sb.Replace("!", "%21"); + sb.Replace("\"", "%22"); + sb.Replace("#", "%23"); + sb.Replace("$", "%24"); + sb.Replace("&", "%26"); + sb.Replace("'", "%27"); + sb.Replace("(", "%28"); + sb.Replace(")", "%29"); + sb.Replace("*", "%2A"); + sb.Replace("+", "%2B"); + sb.Replace(",", "%2C"); + sb.Replace("-", "%2D"); + sb.Replace(".", "%2E"); + sb.Replace("/", "%2F"); + sb.Replace(":", "%3A"); + sb.Replace(";", "%3B"); + sb.Replace("<", "%3C"); + sb.Replace("=", "%3D"); + sb.Replace(">", "%3E"); + sb.Replace("?", "%3F"); + sb.Replace("@", "%40"); + sb.Replace("[", "%5B"); + sb.Replace("\\", "%5C"); + sb.Replace("]", "%5D"); + sb.Replace("{", "%7B"); + sb.Replace("|", "%7C"); + sb.Replace("}", "%7D"); + + return string.Format("{0}={1}", Key, sb.ToString()); + } + } +} diff --git a/src/WebExpress/WebMessage/ParameterDictionary.cs b/src/WebExpress/WebMessage/ParameterDictionary.cs new file mode 100644 index 0000000..17d7657 --- /dev/null +++ b/src/WebExpress/WebMessage/ParameterDictionary.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; + +namespace WebExpress.WebMessage +{ + /// + /// Management of parameters. + /// + public class ParameterDictionary : Dictionary + { + } +} diff --git a/src/WebExpress/WebMessage/ParameterFile.cs b/src/WebExpress/WebMessage/ParameterFile.cs new file mode 100644 index 0000000..a9c468a --- /dev/null +++ b/src/WebExpress/WebMessage/ParameterFile.cs @@ -0,0 +1,56 @@ +namespace WebExpress.WebMessage +{ + public class ParameterFile : Parameter + { + /// + /// Returns the content type. + /// + public string ContentType { get; internal set; } + + /// + /// Returns the data. + /// + public byte[] Data { get; internal set; } + + + /// + /// Constructor + /// + public ParameterFile() + { + } + + /// + /// Constructor + /// + /// The key. + /// The value. + /// The scope of the parameter. + public ParameterFile(string key, string value, ParameterScope scope) + : base(key, value, scope) + { + } + + /// + /// Constructor + /// + /// The key. + /// The value. + /// The scope of the parameter. + public ParameterFile(string key, int value, ParameterScope scope) + : base(key, value, scope) + { + } + + /// + /// Constructor + /// + /// The key. + /// The value. + /// The scope of the parameter. + public ParameterFile(string key, char value, ParameterScope scope) + : base(key, value, scope) + { + } + } +} diff --git a/src/WebExpress/WebMessage/ParameterScope.cs b/src/WebExpress/WebMessage/ParameterScope.cs new file mode 100644 index 0000000..b4eca41 --- /dev/null +++ b/src/WebExpress/WebMessage/ParameterScope.cs @@ -0,0 +1,28 @@ +namespace WebExpress.WebMessage +{ + /// + /// Defines the scopes of the parameter. + /// + public enum ParameterScope + { + /// + /// No classification. + /// + None, + + /// + /// Parameter refers to a part of the uri. + /// + Url, + + /// + /// Parameter refers to the session. + /// + Session, + + /// + /// Parameter refers to url parameters (GET or POST). + /// + Parameter + } +} diff --git a/src/WebExpress/WebMessage/Request.cs b/src/WebExpress/WebMessage/Request.cs new file mode 100644 index 0000000..16f76b9 --- /dev/null +++ b/src/WebExpress/WebMessage/Request.cs @@ -0,0 +1,541 @@ +using Microsoft.AspNetCore.Http.Features; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Net; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using WebExpress.WebComponent; +using WebExpress.WebHtml; +using WebExpress.WebSession; +using WebExpress.WebUri; + +namespace WebExpress.WebMessage +{ + /// + /// See RFC 2616, The Request class encapsulates and extends the + /// original request of the HttpListener call. + /// + public class Request + { + /// + /// The context of the web server. + /// + public IHttpServerContext ServerContext { get; protected set; } + + /// + /// Returns the request method (e.g. POST). + /// + public RequestMethod Method { get; private set; } + + /// + /// Returns the uri. + /// + public UriResource Uri { get; internal set; } + + /// + /// Returns the parameters. + /// + private ParameterDictionary Param { get; } = new ParameterDictionary(); + + /// + /// Returns the session. + /// + public Session Session { get; private set; } + + /// + /// Returns the http version. + /// + public string Protocoll { get; private set; } + + /// + /// Returns the options from the header. + /// + public RequestHeaderFields Header { get; private set; } + + /// + /// Returns the ip address and port number of the server to which the request is made. + /// + public EndPoint LocalEndPoint { get; private set; } + + /// + /// Returns the ip address and port number of the client from which the request originated. + /// + 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. + /// + public UriScheme Scheme { get; private set; } + + /// + /// Returns the request identifier of the incoming http request. + /// + public string RequestTraceIdentifier { get; private set; } + + /// + /// Returns the culture. + /// + public CultureInfo Culture + { + get + { + try + { + // see RFC 5646 + var languages = Header?.AcceptLanguage.FirstOrDefault(); + var language = languages?.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries).FirstOrDefault(); + + return new CultureInfo(language); + } + catch + { + return ServerContext.Culture ?? CultureInfo.CurrentCulture; + } + } + } + + /// + /// Returns the content. + /// + public byte[] Content { get; private set; } + + /// + /// Constructor + /// + /// Initial set of features. + /// The context of the web server. + /// The header. + internal Request(IFeatureCollection contextFeatures, IHttpServerContext serverContext, RequestHeaderFields header) + { + var connectionFeature = contextFeatures.Get(); + var requestFeature = contextFeatures.Get(); + var requestIdentifierFeature = contextFeatures.Get(); + //var sessionFeature = contextFeatures.Get(); + + ServerContext = serverContext; + RequestTraceIdentifier = requestIdentifierFeature.TraceIdentifier; + Protocoll = requestFeature.Protocol; + + Scheme = requestFeature.Scheme.ToLower() switch + { + "http" => UriScheme.Http, + "https" => UriScheme.Https, + "ftp" => UriScheme.FTP, + "file" => UriScheme.File, + "mailto" => UriScheme.Mailto, + "ldap" => UriScheme.Ldap, + _ => UriScheme.Http + + }; + Method = requestFeature.Method.ToUpper() switch + { + "GET" => RequestMethod.GET, + "POST" => RequestMethod.POST, + "PUT" => RequestMethod.PUT, + "DELETE" => RequestMethod.DELETE, + "HEAD" => RequestMethod.HEAD, + "PATCH" => RequestMethod.PATCH, + _ => RequestMethod.GET + }; + + Header = header; + + LocalEndPoint = new IPEndPoint(connectionFeature.LocalIpAddress, connectionFeature.LocalPort); + RemoteEndPoint = new IPEndPoint(connectionFeature.RemoteIpAddress, connectionFeature.RemotePort); + + Uri = new UriResource + ( + Scheme, + new UriAuthority() + { + Host = Header.Host, + Port = connectionFeature.LocalPort + }, + requestFeature.RawTarget + ); + + Content = GetContent(requestFeature.Body, Header.ContentLength); + + ParseQueryParams(requestFeature.QueryString); + ParseRequestParams(); + ParseSessionParams(); + } + + /// + /// Returns the content. + /// + /// The content of a request. + /// The number of bytes sent in the body or zero. + /// Der Content als Byte-Array + internal static byte[] GetContent(Stream body, long? contentLength) + { + if (!contentLength.HasValue || contentLength.Value == 0) + { + return null; + } + + using var ms = new MemoryStream(); + body.CopyTo(ms); + + return ms.ToArray(); + } + + /// + /// Returns the parameters from the reuest query (for example, http://www.example.com?key=value). + /// + /// The query. + private void ParseQueryParams(string query) + { + query = query.TrimStart('?'); + + Parallel.ForEach(query.Split('&'), (param) => + { + if (!string.IsNullOrWhiteSpace(param)) + { + var split = param.Split('='); + + if (split.Length == 1) + { + AddParameter(new Parameter(split[0], null, ParameterScope.Parameter)); + } + else if (split.Length == 2) + { + AddParameter(new Parameter(split[0], split[1], ParameterScope.Parameter)); + } + else if (split.Length > 2) + { + AddParameter(new Parameter(split[0], string.Join("=", split.Skip(1)), ParameterScope.Parameter)); + } + } + }); + } + + /// + /// Parse the request parameters. + /// + private void ParseRequestParams() + { + if (string.IsNullOrWhiteSpace(Header.ContentType)) + { + return; + } + + var contentType = Header.ContentType?.Split(';'); + //var contentStr = Encoding.UTF8.GetString(Content); + + switch (TypeEnctypeExtensions.Convert(contentType.FirstOrDefault())) + { + case TypeEnctype.None: + { + var boundary = Header.ContentType; + var boundaryValue = "--" + boundary?.Split('=').Skip(1)?.FirstOrDefault(); + var offset = 0; + int pos = 0; + var dispositions = new List>(); // Item1=Position, Item2=Länge + + // determine dispositions + for (var i = 0; i < Content.Length; i++) + { + if (Content[i] == '\r') + { + var c = Encoding.UTF8.GetString(Content, offset, boundaryValue.Length).Trim(); + if (c.StartsWith(boundaryValue)) + { + if (i - boundaryValue.Length - pos > 0) + { + dispositions.Add(new Tuple(pos, i - boundaryValue.Length - pos)); + } + + pos = i + 2; + + if (c.EndsWith("--")) + { + break; + } + } + } + else if (Content[i] == '\n') + { + offset = i + 1; + } + else if (i == Content.Length - 1) + { + // at the end + var c = Encoding.UTF8.GetString(Content, offset, boundaryValue.Length).Trim(); + if (c.StartsWith(boundaryValue)) + { + dispositions.Add(new Tuple(pos, i - boundaryValue.Length - pos)); + } + } + } + + foreach (var item in dispositions) + { + var disposition = string.Empty; + var name = string.Empty; + var filename = string.Empty; + var contenttype = string.Empty; + offset = 0; + + var str = Encoding.UTF8.GetString(Content, item.Item1, item.Item2 > 256 ? 256 : item.Item2); + var match = Regex.Match(str, @"^Content-Disposition: (.*)$", RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.Compiled); + if (match.Groups[1].Success) + { + offset += match.Length + 1; // + Zeilenende + var dispositionParam = match.Groups[1].ToString().Split(';'); + disposition = dispositionParam.FirstOrDefault(); + foreach (var v in dispositionParam.Skip(1)) + { + match = Regex.Match(v.Trim(), @"^name=""(.*)""$", RegexOptions.IgnoreCase | RegexOptions.Compiled); + if (match.Groups[1].Success) + { + name = match.Groups[1].ToString(); + } + + match = Regex.Match(v.Trim(), @"^filename=""(.*)""$", RegexOptions.IgnoreCase | RegexOptions.Compiled); + if (match.Groups[1].Success) + { + filename = match.Groups[1].ToString(); + } + } + } + + match = Regex.Match(str, @"^Content-Type: (.*)$", RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.Compiled); + if (match.Groups[1].Success) + { + offset += match.Length + 1; // + End of line + contenttype = match.Groups[1].ToString().Trim(); + } + + if (string.IsNullOrWhiteSpace(filename)) + { + offset += 2; // + blank line + if (item.Item2 - offset - 1 >= 0) + { + var value = Encoding.UTF8.GetString(Content, item.Item1 + offset, item.Item2 - offset - 2); + + var param = new Parameter(name, value.TrimEnd(), ParameterScope.Parameter); + AddParameter(param); + } + else + { + var param = new Parameter(name, string.Empty, ParameterScope.Parameter); + AddParameter(param); + } + } + else + { + offset += 2; // + blank line + if (item.Item2 - offset - 1 >= 0) + { + var bytes = new byte[item.Item2 - offset - 2]; + Buffer.BlockCopy(Content, item.Item1 + offset, bytes, 0, item.Item2 - offset - 2); + + var param = new ParameterFile(name, filename, ParameterScope.Parameter) { ContentType = contenttype, Data = bytes }; + AddParameter(param); + } + else + { + var param = new Parameter(name, filename, ParameterScope.Parameter); + AddParameter(param); + } + } + } + + break; + } + case TypeEnctype.Text: + { + var lines = new List(); + var offset = 0; + + for (var i = 0; i < Content.Length; i++) + { + if (Content[i] == '\r') + { + lines.Add(Encoding.UTF8.GetString(Content, offset, i - offset)); + } + else if (Content[i] == '\n') + { + offset = i + 1; + } + } + + // if not all bytes have been read yet + if (offset < Content.Length) + { + lines.Add(Encoding.UTF8.GetString(Content, offset, Content.Length - offset)); + } + + var last = default(Parameter); + + foreach (var v in lines) + { + var match = Regex.Match(v, @"([\w-]*)=(.*)", RegexOptions.Compiled); + if (match.Groups[1].Success && match.Groups[2].Success) + { + last = new Parameter(match.Groups[1].ToString().Trim(), match.Groups[2].ToString().Trim(), ParameterScope.Parameter); + AddParameter(last); + } + else if (last != null) + { + last.Value += "\r\n" + v; + + } + } + + if (last != null) + { + last.Value = last.Value.TrimEnd(); + } + + break; + } + case TypeEnctype.UrLEncoded: + { + var str = Encoding.UTF8.GetString(Content, 0, Content.Length); + var param = str.Replace('+', ' '); + + foreach (var v in param.Split('&')) + { + var s = v.Split('='); + AddParameter(new Parameter + ( + s[0], + s.Length > 1 ? s[1]?.TrimEnd() : string.Empty, + ParameterScope.Parameter + )); + } + + break; + } + default: + { + + break; + } + } + } + + /// + /// Parse the session parameters. + /// + private void ParseSessionParams() + { + Session = ComponentManager.SessionManager.GetSession(this); + + var property = Session.GetProperty(); + if (property != null && property.Params != null) + { + foreach (var param in property.Params) + { + AddParameter(new Parameter(param.Key?.ToLower(), param.Value.Value, ParameterScope.Session)); + } + } + } + + /// + /// Adds several parameters. + /// + /// The parameters. + public void AddParameter(IEnumerable param) + { + foreach (var p in param) + { + AddParameter(p); + } + } + + /// + /// Adds one parameter. + /// + /// The parameter. + public void AddParameter(Parameter param) + { + if (!Param.ContainsKey(param.Key.ToLower())) + { + Param.Add(param.Key.ToLower(), param); + } + else + { + Param[param.Key.ToLower()] = param; + } + } + + /// + /// Returns a parameter by name. + /// + /// The name of the parameter. + /// The value. + public Parameter GetParameter(string name) + { + if (!string.IsNullOrWhiteSpace(name) && HasParameter(name)) + { + return Param[name.ToLower()]; + } + + return null; + } + + /// + /// Returns a parameter by name. + /// + /// The parameter. + /// The value. + public Parameter GetParameter() where T : Parameter + { + var name = Parameter.GetKey(); + + if (!string.IsNullOrWhiteSpace(name) && HasParameter(name)) + { + return Param[name.ToLower()]; + } + + return null; + } + + /// + /// Checks whether a parameter exists. + /// + /// The name of the parameter. + /// True if parameters are present, false otherwise. + public bool HasParameter(string name) + { + if (name == null) + { + return false; + } + + return Param.ContainsKey(name.ToLower()); + } + } +} diff --git a/src/WebExpress/WebMessage/RequestAuthorization.cs b/src/WebExpress/WebMessage/RequestAuthorization.cs new file mode 100644 index 0000000..49f9778 --- /dev/null +++ b/src/WebExpress/WebMessage/RequestAuthorization.cs @@ -0,0 +1,61 @@ +using System; +using System.Linq; +using System.Text.RegularExpressions; + +namespace WebExpress.WebMessage +{ + public class RequestAuthorization + { + /// + /// Returns or sets the type.e (Basic bei WWW-Authenticate: Basic realm="RealmName") + /// + public string Type { get; set; } + + /// + /// Liefert oder setzt den Loginnamen + /// + public string Identification { get; set; } + + /// + /// Liefert oder setzt das Passwort + /// + public string Password { get; set; } + + /// + /// Parst die Authorization-Anforderung + /// + /// Authorizations-Zeichenkette z.B. Basic d2lraTpwZWRpYQ== + /// + public static RequestAuthorization Parse(string str) + { + if (str == null) + { + return null; + } + + var m = Regex.Match(str, "^(.*) (.*)$"); + var type = "Basic"; + var user = ""; + var password = ""; + + if (m.Success && m.Groups.Count >= 3) + { + type = m.Groups[1].Value; + var userPw = m.Groups[2].Value; + userPw = System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(userPw)); + + var split = userPw.Split(':'); + + user = split[0]; + password = split.Count() > 0 ? split[1] : ""; + } + + return new RequestAuthorization() + { + Type = type, + Identification = user, + Password = password + }; + } + } +} diff --git a/src/WebExpress/WebMessage/RequestHeaderFields.cs b/src/WebExpress/WebMessage/RequestHeaderFields.cs new file mode 100644 index 0000000..af984ae --- /dev/null +++ b/src/WebExpress/WebMessage/RequestHeaderFields.cs @@ -0,0 +1,114 @@ +using Microsoft.AspNetCore.Http.Features; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Text; + +namespace WebExpress.WebMessage +{ + /// + /// see RFC 2616 + /// + public class RequestHeaderFields + { + /// + /// Returns the host. + /// + public string Host { get; private set; } + + /// + /// Returns the connection. Keep-Alive or close. + /// + public string Connection { get; private set; } + + /// + /// Returns the content length. + /// + public long ContentLength { get; private set; } + + /// + /// Returns the content type. + /// + public string ContentType { get; private set; } + + /// + /// Returns the language of the content. + /// + public string ContentLanguage { get; private set; } + + /// + /// Returns the encoding of the content. + /// + public Encoding ContentEncoding { get; private set; } + + /// + /// Returns the user agent. + /// + public string UserAgent { get; private set; } + + /// + /// Returns the accepted media types. + /// + public ICollection Accept { get; private set; } + + /// + /// Returns the accepted encodings. + /// + public string AcceptEncoding { get; private set; } + + /// + /// Returns the accepted languages. + /// + public IEnumerable AcceptLanguage { get; private set; } + + /// + /// Returns the access data name and password. + /// + public RequestAuthorization Authorization { get; private set; } + + /// + /// Returns the cookies. + /// + public ICollection Cookies { get; } = new List(); + + /// + /// Returns the referer. The referer header echoes the absolute or partial address from + /// which a resource was requested. The Referer header allows a server to identify referring + /// pages from which people visit or where requested resources are used. + /// + public string Referer { get; private set; } + + /// + /// Constructor + /// + /// Initial set of features. + internal RequestHeaderFields(IFeatureCollection contextFeatures) + { + var requestFeature = contextFeatures.Get(); + + Host = requestFeature.Headers.Host; + Connection = requestFeature.Headers.Connection; + 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; + 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; + + 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)); + } + + Authorization = RequestAuthorization.Parse(requestFeature.Headers.Authorization); + } + } +} diff --git a/src/WebExpress/WebMessage/RequestMethod.cs b/src/WebExpress/WebMessage/RequestMethod.cs new file mode 100644 index 0000000..5fd4fe6 --- /dev/null +++ b/src/WebExpress/WebMessage/RequestMethod.cs @@ -0,0 +1,35 @@ +namespace WebExpress.WebMessage +{ + public enum RequestMethod + { + NONE, + GET, + POST, + PUT, + HEAD, + DELETE, + PATCH // RFC 5789 + } + + public static class RequestMethodExtensions + { + /// + /// Umwandlung in eine CSS-Klasse + /// + /// Das Layout, welches umgewandelt werden soll + /// Die zum Layout gehörende CSS-KLasse + public static string ToString(this RequestMethod layout) + { + return layout switch + { + RequestMethod.GET => "GET", + RequestMethod.POST => "POST", + RequestMethod.PUT => "PUT", + RequestMethod.HEAD => "HEAD", + RequestMethod.DELETE => "DELETE", + RequestMethod.PATCH => "PATCH", + _ => string.Empty, + }; + } + } +} diff --git a/src/WebExpress/WebMessage/Response.cs b/src/WebExpress/WebMessage/Response.cs new file mode 100644 index 0000000..f01d570 --- /dev/null +++ b/src/WebExpress/WebMessage/Response.cs @@ -0,0 +1,35 @@ +namespace WebExpress.WebMessage +{ + /// + /// siehe RFC 2616 Tz. 6 + /// + public class Response + { + /// + /// Setzt oder liefert die Optionen + /// + public ResponseHeaderFields Header { get; } = new ResponseHeaderFields(); + + /// + /// Setzt oder liefert den Content + /// + public object Content { get; set; } + + /// + /// Liefert oder setzt den Statuscode + /// + public int Status { get; protected set; } + + /// + /// Liefert oder setzt den Statustext + /// + public string Reason { get; protected set; } + + /// + /// Constructor + /// + protected Response() + { + } + } +} diff --git a/src/WebExpress/WebMessage/ResponseBadRequest.cs b/src/WebExpress/WebMessage/ResponseBadRequest.cs new file mode 100644 index 0000000..9b9955e --- /dev/null +++ b/src/WebExpress/WebMessage/ResponseBadRequest.cs @@ -0,0 +1,22 @@ +namespace WebExpress.WebMessage +{ + /// + /// siehe RFC 2616 Tz. 6 + /// + public class ResponseBadRequest : Response + { + /// + /// Constructor + /// + public ResponseBadRequest() + { + var content = "404404 - Bad Request"; + Status = 400; + Reason = "Bad Request"; + + Header.ContentType = "text/html"; + Header.ContentLength = content.Length; + Content = content; + } + } +} diff --git a/src/WebExpress/WebMessage/ResponseForbidden.cs b/src/WebExpress/WebMessage/ResponseForbidden.cs new file mode 100644 index 0000000..bde430f --- /dev/null +++ b/src/WebExpress/WebMessage/ResponseForbidden.cs @@ -0,0 +1,22 @@ +namespace WebExpress.WebMessage +{ + /// + /// siehe RFC 2616 Tz. 6 + /// + public class ResponseForbidden : Response + { + /// + /// Constructor + /// + public ResponseForbidden() + { + var content = "403403 - Forbidden"; + Status = 403; + Reason = "Forbidden"; + + Header.ContentType = "text/html"; + Header.ContentLength = content.Length; + Content = content; + } + } +} diff --git a/src/WebExpress/WebMessage/ResponseHeaderFields.cs b/src/WebExpress/WebMessage/ResponseHeaderFields.cs new file mode 100644 index 0000000..e9f86e4 --- /dev/null +++ b/src/WebExpress/WebMessage/ResponseHeaderFields.cs @@ -0,0 +1,130 @@ +using System.Collections.Generic; +using System.Net; +using System.Text; + +namespace WebExpress.WebMessage +{ + /// + /// siehe RFC 2616 + /// + public class ResponseHeaderFields + { + /// + /// Liefert oder setzt die Content-Länge + /// + public int ContentLength { get; set; } + + /// + /// Liefert oder setzt den Content-Typ + /// + public string ContentType { get; set; } + + /// + /// Liefert oder setzt die Sprache des Content + /// + public string ContentLanguage { get; set; } + + /// + /// Liefert oder setzt die Direktiven für das Caching (siehe RFC 7234) + /// + public string CacheControl { get; set; } + + /// + /// ContentDisposition + /// + public string ContentDisposition { get; set; } + + /// + /// Die Basic Authentication (Basisauthentifizierung) nach RFC 2617 + /// + public bool WWWAuthenticate { get; set; } + + /// + /// Location + /// + public string Location { get; set; } + + /// + /// Benutzerdefinierte Header + /// + public Dictionary CustomHeader { get; private set; } + + /// + /// Liefert oder setzt die Cookies + /// + public CookieCollection Cookies { get; } = new CookieCollection(); + + /// + /// Constructor + /// + public ResponseHeaderFields() + { + CustomHeader = new Dictionary(); + WWWAuthenticate = false; + ContentLength = -1; + } + + /// + /// Setzt ein benutzerdefinierten Header + /// + /// + /// + public void AddCustomHeader(string key, string value) + { + if (!CustomHeader.ContainsKey(key)) + { + CustomHeader.Add(key, value); + } + else + { + CustomHeader[key] = value; + } + } + + /// + /// In Stringform umwandeln + /// + /// + public override string ToString() + { + var sb = new StringBuilder(); + + if (!string.IsNullOrWhiteSpace(ContentType)) + { + sb.AppendLine("Content-Type: " + ContentType); + } + + if (ContentLength > -1) + { + sb.AppendLine("Content-Length:" + ContentLength); + } + + if (!string.IsNullOrWhiteSpace(ContentDisposition)) + { + sb.AppendLine("Content-Disposition: " + ContentDisposition); + } + + if (!string.IsNullOrWhiteSpace(CacheControl)) + { + sb.AppendLine("Cache-Control: " + CacheControl); + } + + if (WWWAuthenticate) + { + sb.AppendLine("WWW-Authenticate: Basic realm=\"Bereich\""); + } + + if (!string.IsNullOrWhiteSpace(Location)) + { + sb.AppendLine("Location: " + Location); + } + + foreach (var c in CustomHeader) + { + sb.AppendLine(c.Key + ": " + c.Value); + } + + return sb.ToString(); + } + } +} diff --git a/src/WebExpress/WebMessage/ResponseInternalServerError.cs b/src/WebExpress/WebMessage/ResponseInternalServerError.cs new file mode 100644 index 0000000..5c259c3 --- /dev/null +++ b/src/WebExpress/WebMessage/ResponseInternalServerError.cs @@ -0,0 +1,22 @@ +namespace WebExpress.WebMessage +{ + /// + /// siehe RFC 2616 Tz. 6 + /// + public class ResponseInternalServerError : Response + { + /// + /// Constructor + /// + public ResponseInternalServerError() + { + var content = "404500 - Internal Server Error"; + Status = 500; + Reason = "Internal Server Error"; + + Header.ContentType = "text/html"; + Header.ContentLength = content.Length; + Content = content; + } + } +} diff --git a/src/WebExpress/WebMessage/ResponseNotFound.cs b/src/WebExpress/WebMessage/ResponseNotFound.cs new file mode 100644 index 0000000..c2d71e5 --- /dev/null +++ b/src/WebExpress/WebMessage/ResponseNotFound.cs @@ -0,0 +1,22 @@ +namespace WebExpress.WebMessage +{ + /// + /// siehe RFC 2616 Tz. 6 + /// + public class ResponseNotFound : Response + { + /// + /// Constructor + /// + public ResponseNotFound() + { + var content = "404404 - Not Found"; + Status = 404; + Reason = "Not Found"; + + Header.ContentType = "text/html"; + Header.ContentLength = content.Length; + Content = content; + } + } +} diff --git a/src/WebExpress/WebMessage/ResponseOK.cs b/src/WebExpress/WebMessage/ResponseOK.cs new file mode 100644 index 0000000..8b54607 --- /dev/null +++ b/src/WebExpress/WebMessage/ResponseOK.cs @@ -0,0 +1,17 @@ +namespace WebExpress.WebMessage +{ + /// + /// siehe RFC 2616 Tz. 6 + /// + public class ResponseOK : Response + { + /// + /// Constructor + /// + public ResponseOK() + { + Status = 200; + Reason = "OK"; + } + } +} diff --git a/src/WebExpress/WebMessage/ResponseRedirectPermanentlyMoved.cs b/src/WebExpress/WebMessage/ResponseRedirectPermanentlyMoved.cs new file mode 100644 index 0000000..a1f528f --- /dev/null +++ b/src/WebExpress/WebMessage/ResponseRedirectPermanentlyMoved.cs @@ -0,0 +1,19 @@ +namespace WebExpress.WebMessage +{ + /// + /// siehe RFC 2616 Tz. 6 + /// + public class ResponseRedirectPermanentlyMoved : Response + { + /// + /// Constructor + /// + public ResponseRedirectPermanentlyMoved(string location) + { + Status = 301; + Reason = "permanently moved"; + + Header.Location = location; + } + } +} diff --git a/src/WebExpress/WebMessage/ResponseRedirectTemporarilyMoved.cs b/src/WebExpress/WebMessage/ResponseRedirectTemporarilyMoved.cs new file mode 100644 index 0000000..18343b0 --- /dev/null +++ b/src/WebExpress/WebMessage/ResponseRedirectTemporarilyMoved.cs @@ -0,0 +1,22 @@ +namespace WebExpress.WebMessage +{ + /// + /// siehe RFC 2616 Tz. 6 + /// + public class ResponseRedirectTemporarilyMoved : Response + { + /// + /// Constructor + /// + public ResponseRedirectTemporarilyMoved(string location) + { + Status = 302; + Reason = "temporarily moved"; + //Content = ""; + + //HeaderFields.ContentType = "text/html"; + //HeaderFields.ContentLength = Content.ToString().Length; + Header.Location = location; + } + } +} diff --git a/src/WebExpress/WebMessage/ResponseUnauthorized.cs b/src/WebExpress/WebMessage/ResponseUnauthorized.cs new file mode 100644 index 0000000..40229c4 --- /dev/null +++ b/src/WebExpress/WebMessage/ResponseUnauthorized.cs @@ -0,0 +1,19 @@ +namespace WebExpress.WebMessage +{ + /// + /// siehe RFC 2616 Tz. 6 + /// + public class ResponseUnauthorized : Response + { + /// + /// Constructor + /// + public ResponseUnauthorized() + { + Status = 401; + Reason = "OK"; + + Header.WWWAuthenticate = true; + } + } +} diff --git a/src/WebExpress/WebModule/IModule.cs b/src/WebExpress/WebModule/IModule.cs new file mode 100644 index 0000000..c712b1a --- /dev/null +++ b/src/WebExpress/WebModule/IModule.cs @@ -0,0 +1,18 @@ +using System; + +namespace WebExpress.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/WebModule/IModuleContext.cs b/src/WebExpress/WebModule/IModuleContext.cs new file mode 100644 index 0000000..43cb2aa --- /dev/null +++ b/src/WebExpress/WebModule/IModuleContext.cs @@ -0,0 +1,54 @@ +using WebExpress.WebApplication; +using WebExpress.WebPlugin; +using WebExpress.WebUri; + +namespace WebExpress.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/WebModule/ModuleContext.cs b/src/WebExpress/WebModule/ModuleContext.cs new file mode 100644 index 0000000..8967d1b --- /dev/null +++ b/src/WebExpress/WebModule/ModuleContext.cs @@ -0,0 +1,70 @@ +using WebExpress.WebApplication; +using WebExpress.WebPlugin; +using WebExpress.WebUri; + +namespace WebExpress.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/WebModule/ModuleDictionary.cs b/src/WebExpress/WebModule/ModuleDictionary.cs new file mode 100644 index 0000000..4252931 --- /dev/null +++ b/src/WebExpress/WebModule/ModuleDictionary.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; +using WebExpress.WebPlugin; + +namespace WebExpress.WebModule +{ + /// + /// Key = plugin context + /// Value = { Key = module id, Value = module item } + /// + internal class ModuleDictionary : Dictionary> + { + } +} diff --git a/src/WebExpress/WebModule/ModuleItem.cs b/src/WebExpress/WebModule/ModuleItem.cs new file mode 100644 index 0000000..504fde2 --- /dev/null +++ b/src/WebExpress/WebModule/ModuleItem.cs @@ -0,0 +1,283 @@ +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.Internationalization; +using WebExpress.WebApplication; +using WebExpress.WebPlugin; +using WebExpress.WebUri; + +namespace WebExpress.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/WebModule/ModuleItemInstance.cs b/src/WebExpress/WebModule/ModuleItemInstance.cs new file mode 100644 index 0000000..9626c4f --- /dev/null +++ b/src/WebExpress/WebModule/ModuleItemInstance.cs @@ -0,0 +1,22 @@ +using System.Threading; + +namespace WebExpress.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/WebModule/ModuleManager.cs b/src/WebExpress/WebModule/ModuleManager.cs new file mode 100644 index 0000000..e03b57e --- /dev/null +++ b/src/WebExpress/WebModule/ModuleManager.cs @@ -0,0 +1,446 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using WebExpress.Internationalization; +using WebExpress.WebApplication; +using WebExpress.WebAttribute; +using WebExpress.WebComponent; +using WebExpress.WebPlugin; +using WebExpress.WebUri; + +namespace WebExpress.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/WebPackage/PackageBuilder.cs b/src/WebExpress/WebPackage/PackageBuilder.cs new file mode 100644 index 0000000..6907216 --- /dev/null +++ b/src/WebExpress/WebPackage/PackageBuilder.cs @@ -0,0 +1,290 @@ +using System; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Xml.Serialization; + +namespace WebExpress.WebPackage +{ + /// + /// Class for generating webexpress packages from a build environment. + /// + public static class PackageBuilder + { + /// + /// Creates a webex package. + /// + /// The spec file (*.spec). + /// The config. Debug or Release. + /// The target frameworks. Semicolon separated list of target framework moniker (TFM). + /// The output directory. + public static void Create(string specFile, string config, string targets, string outputDirectory) + { + Console.WriteLine($"*** PackageBuilder: specFile '{specFile}'."); + Console.WriteLine($"*** PackageBuilder: config '{config}'."); + Console.WriteLine($"*** PackageBuilder: targets '{targets}'."); + Console.WriteLine($"*** PackageBuilder: outputDirectory '{outputDirectory}'."); + + var rootDirectory = Path.GetDirectoryName(specFile); + using var fileStream = File.OpenRead(specFile); + var serializer = new XmlSerializer(typeof(PackageItemSpec)); + var package = (PackageItemSpec)serializer.Deserialize(fileStream); + var zipFileType = package.Id.Equals("WebExpress") ? "zip" : "wxp"; + + Console.WriteLine($"*** PackageBuilder: Creates a webex package '{package.Id}' in directory '{outputDirectory}'."); + + if (!Directory.Exists(outputDirectory)) + { + Directory.CreateDirectory(outputDirectory); + } + + using var zipFileStream = new FileStream(Path.Combine(outputDirectory, $"{package.Id}.{package.Version}.{zipFileType}"), FileMode.Create); + using var archive = new ZipArchive(zipFileStream, ZipArchiveMode.Create, true); + + // find readme + if (!string.IsNullOrWhiteSpace(package.Readme)) + { + var file = Find(rootDirectory, Path.GetFileName(package.Readme)); + + if (File.Exists(file)) + { + ReadmeToZip + ( + archive, + File.ReadAllBytes(file) + ); + } + } + + // privacypolicy.md + if (!string.IsNullOrWhiteSpace(package.PrivacyPolicy)) + { + var file = Find(rootDirectory, Path.GetFileName(package.PrivacyPolicy)); + + if (File.Exists(file)) + { + PrivacyPolicyToZip + ( + archive, + File.ReadAllBytes(file) + ); + } + } + + // find icon + if (!string.IsNullOrWhiteSpace(package.Icon)) + { + var file = Find(rootDirectory, Path.GetFileName(package.Icon)); + + if (File.Exists(file)) + { + IconToZip + ( + archive, + Path.GetFileName(package.Icon), + File.ReadAllBytes(file) + ); + } + } + + // find licenses + foreach (var file in Directory.GetFiles(Path.GetDirectoryName(specFile), "*.lic", SearchOption.AllDirectories)) + { + if (File.Exists(file)) + { + LicensesToZip + ( + archive, + Path.GetFileName(file), + File.ReadAllBytes(file) + ); + } + } + + if (!package.Id.Equals("WebExpress")) + { + SpecToZip(archive, package); + } + + ProjectToZip(archive, package, rootDirectory, config, targets); + ArtifactsToZip(archive, package, rootDirectory); + } + + /// + /// Create the readme file. + /// + /// The zip archive. + /// The readme content. + private static void ReadmeToZip(ZipArchive archive, byte[] readme) + { + var zipArchiveEntry = archive.CreateEntry("readme.md", CompressionLevel.Fastest); + using var zipStream = zipArchiveEntry.Open(); + zipStream.Write(readme, 0, readme.Length); + + Console.WriteLine($"*** PackageBuilder: Create the readme file."); + } + + /// + /// Create the privacy policy file. + /// + /// The zip archive. + /// The privacy policy content. + private static void PrivacyPolicyToZip(ZipArchive archive, byte[] readme) + { + var zipArchiveEntry = archive.CreateEntry("privacypolicy.md", CompressionLevel.Fastest); + using var zipStream = zipArchiveEntry.Open(); + zipStream.Write(readme, 0, readme.Length); + + Console.WriteLine($"*** PackageBuilder: Create the privacy policy file."); + } + + /// + /// Create the icon file. + /// + /// The zip archive. + /// The icon file name. + /// The icon content. + private static void IconToZip(ZipArchive archive, string fileName, byte[] icon) + { + var zipArchiveEntry = archive.CreateEntry($"icon{Path.GetExtension(fileName)}", CompressionLevel.Fastest); + using var zipStream = zipArchiveEntry.Open(); + zipStream.Write(icon, 0, icon.Length); + + Console.WriteLine($"*** PackageBuilder: Create the icon file."); + } + + /// + /// Create the licenses file. + /// + /// The zip archive. + /// The licenses file name. + /// The licenses content. + private static void LicensesToZip(ZipArchive archive, string fileName, byte[] icon) + { + var zipArchiveEntry = archive.CreateEntry($"licenses/{Path.GetFileNameWithoutExtension(fileName)}.txt", CompressionLevel.Fastest); + using var zipStream = zipArchiveEntry.Open(); + zipStream.Write(icon, 0, icon.Length); + + Console.WriteLine($"*** PackageBuilder: Create the licenses file."); + } + + /// + /// Create the spec file. + /// + /// The zip archive. + /// The package. + private static void SpecToZip(ZipArchive archive, PackageItemSpec package) + { + var zipBinarys = package.Id.Equals("WebExpress") ? "bin" : "lib"; + var zipArchiveEntry = archive.CreateEntry($"{package.Id}.spec", CompressionLevel.Fastest); + var serializer = new XmlSerializer(typeof(PackageItemSpec)); + using var zipStream = zipArchiveEntry.Open(); + + var newPackage = new PackageItemSpec() + { + Id = package.Id, + Version = package.Version, + Title = package.Title, + Authors = package.Authors, + License = package.License, + LicenseUrl = package.LicenseUrl, + Icon = $"icon{Path.GetExtension(package.Icon)}", + Readme = $"readme.md", + Description = package.Description, + Tags = package.Tags, + Plugins = package.Plugins?.Select(x => $"{zipBinarys}/{Path.GetFileName(x)}").ToArray(), + }; + + serializer.Serialize(zipStream, newPackage); + + Console.WriteLine($"*** PackageBuilder: Create the spec file."); + } + + /// + /// Copy the plugin lib files to zip. + /// + /// The zip archive. + /// The package. + /// The root path. + /// The config. Debug or Release. + /// The target frameworks. Semicolon separated list of target framework moniker (TFM). + private static void ProjectToZip(ZipArchive archive, PackageItemSpec package, string path, string config, string targets) + { + var zipBinarys = package.Id.Equals("WebExpress") ? "bin" : "lib"; + + foreach (var plugin in package?.Plugins ?? Enumerable.Empty()) + { + var pluginName = Path.GetFileName(plugin); + foreach (var target in targets?.Split(';', StringSplitOptions.RemoveEmptyEntries) ?? Enumerable.Empty()) + { + var dir = Path.Combine(path, plugin, "bin", config, target); + + foreach (var fileName in Directory.GetFiles(dir, "*.*", SearchOption.AllDirectories)) + { + if (!string.IsNullOrWhiteSpace(fileName) && File.Exists(fileName)) + { + var item = fileName.Replace(dir, ""); + var fileData = File.ReadAllBytes(fileName); + var zipArchiveEntry = archive.CreateEntry($"{zipBinarys}/{pluginName}/{target}{item}", CompressionLevel.Fastest); + using var zipStream = zipArchiveEntry.Open(); + zipStream.Write(fileData, 0, fileData.Length); + + Console.WriteLine($"*** PackageBuilder: Copy the output file '{item}' to {pluginName}."); + } + } + } + } + } + + /// + /// Create the artifact files. + /// + /// The zip archive. + /// The package. + /// The root path. + private static void ArtifactsToZip(ZipArchive archive, PackageItemSpec package, string path) + { + var zipBinarys = package.Id.Equals("WebExpress") ? "bin" : "lib"; + + foreach (var item in package?.Artifacts ?? Enumerable.Empty()) + { + var fileName = Find(path, item); + + if (!string.IsNullOrWhiteSpace(fileName) && File.Exists(fileName)) + { + var fileData = File.ReadAllBytes(fileName); + var zipArchiveEntry = archive.CreateEntry($"{zipBinarys}/{item}", CompressionLevel.Fastest); + using var zipStream = zipArchiveEntry.Open(); + zipStream.Write(fileData, 0, fileData.Length); + + Console.WriteLine($"*** PackageBuilder: Create the artifact file '{fileName}'."); + } + } + } + + /// + /// Find a file + /// + /// The path. + /// The file name. + /// The file name, if found or null. + private static string Find(string path, string fileName) + { + try + { + foreach (var f in Directory.GetFiles(path, "*.*", SearchOption.AllDirectories) + .Where(x => x.Replace('\\', '/').EndsWith(fileName.Replace('\\', '/'), StringComparison.OrdinalIgnoreCase))) + { + return f; + } + + path = Directory.GetParent(path)?.FullName; + } + catch + { + } + + return null; + } + } +} diff --git a/src/WebExpress/WebPackage/PackageCatalog.cs b/src/WebExpress/WebPackage/PackageCatalog.cs new file mode 100644 index 0000000..0d03fbb --- /dev/null +++ b/src/WebExpress/WebPackage/PackageCatalog.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Xml.Serialization; + +namespace WebExpress.WebPackage +{ + [XmlRoot("catalog")] + public class PackageCatalog + { + /// + /// Returns the package entries in the catalog. + /// + [XmlElement("package")] + public List Packages { get; } = new List(); + + /// + /// Returns the system package entries in the catalog. + /// + [XmlIgnore] + public List SystemPackages { get; } = new List(); + + /// + /// Locates a specific catalog item. + /// + /// The package id. + /// The catalog item or null. + public PackageCatalogItem Find(string id) + { + return Packages + .Where(x => x.Id.Equals(id, StringComparison.OrdinalIgnoreCase)) + .FirstOrDefault(); + } + } +} diff --git a/src/WebExpress/WebPackage/PackageCatalogItem.cs b/src/WebExpress/WebPackage/PackageCatalogItem.cs new file mode 100644 index 0000000..4fdaac6 --- /dev/null +++ b/src/WebExpress/WebPackage/PackageCatalogItem.cs @@ -0,0 +1,49 @@ +using System.Collections.Generic; +using System.Xml.Serialization; +using WebExpress.WebPlugin; + +namespace WebExpress.WebPackage +{ + [XmlRoot("package")] + public class PackageCatalogItem + { + /// + /// Returns or sets Returns or sets the id. + /// + [XmlAttribute("id")] + public string Id { get; set; } + + /// + /// Returns or sets the filename. + /// + [XmlAttribute("file")] + public string File { get; set; } + + /// + /// Returns or sets the state. + /// + [XmlAttribute("state")] + public PackageCatalogeItemState State { get; set; } + + /// + /// Returns the plugins belonging to the package. + /// + [XmlIgnore] + public List Plugins { get; internal set; } = new List(); + + /// + /// Returns the meta information about the package. + /// + [XmlIgnore] + public PackageItem Metadata { get; set; } + + /// + /// Conversion into a string representation of the object. + /// + /// The object as a string. + public override string ToString() + { + return Id; + } + } +} diff --git a/src/WebExpress/WebPackage/PackageCatalogeItemState.cs b/src/WebExpress/WebPackage/PackageCatalogeItemState.cs new file mode 100644 index 0000000..4ecbe6e --- /dev/null +++ b/src/WebExpress/WebPackage/PackageCatalogeItemState.cs @@ -0,0 +1,20 @@ +namespace WebExpress.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/WebPackage/PackageItem.cs b/src/WebExpress/WebPackage/PackageItem.cs new file mode 100644 index 0000000..cf078f9 --- /dev/null +++ b/src/WebExpress/WebPackage/PackageItem.cs @@ -0,0 +1,69 @@ +using System.Collections.Generic; + +namespace WebExpress.WebPackage +{ + public class PackageItem + { + /// + /// Returns or sets the package file name. + /// + public string FileName { get; set; } + + /// + /// Returns or sets Returns or sets the id. + /// + public string Id { get; set; } + + /// + /// Returns or sets the version. + /// + public string Version { get; set; } + + /// + /// Returns or sets the titles. + /// + public string Title { get; set; } + + /// + /// Returns or sets the authors. + /// + public string Authors { get; set; } + + /// + /// Returns or sets the license. + /// + public string License { get; set; } + + /// + /// Returns or sets the package icon. + /// + public string Icon { get; set; } + + /// + /// Returns or sets the readme file of the package (md format). + /// + public string Readme { get; set; } + + /// + /// Returns or sets the description. + /// + public string Description { get; set; } + + /// + /// Returns or sets the tags. + /// + public string Tags { get; set; } + + /// + /// Returns or sets the plugin sources. + /// + public IEnumerable PluginSources { get; set; } + + /// + /// Constructor + /// + internal PackageItem() + { + } + } +} diff --git a/src/WebExpress/WebPackage/PackageItemSpec.cs b/src/WebExpress/WebPackage/PackageItemSpec.cs new file mode 100644 index 0000000..cbeb0fa --- /dev/null +++ b/src/WebExpress/WebPackage/PackageItemSpec.cs @@ -0,0 +1,89 @@ +using System.Xml.Serialization; + +namespace WebExpress.WebPackage +{ + /// + /// The package specification is an XML file containing the specification of the package. + /// + [XmlRoot("package")] + public class PackageItemSpec + { + /// + /// Returns or sets the package id. + /// + [XmlElement("id")] + public string Id { get; set; } + + /// + /// Returns or sets the package version. + /// + [XmlElement("version")] + public string Version { get; set; } + + /// + /// Returns or sets the titles. + /// + [XmlElement("title")] + public string Title { get; set; } + + /// + /// Returns or sets the authors. + /// + [XmlElement("authors", IsNullable = true)] + public string Authors { get; set; } + + /// + /// Returns or sets the license. + /// + [XmlElement("license", IsNullable = true)] + public string License { get; set; } + + /// + /// Returns or sets the url of the license. + /// + [XmlElement("licenseUrl", IsNullable = true)] + public string LicenseUrl { get; set; } + + /// + /// Returns or sets the icon of the package. + /// + [XmlElement("icon", IsNullable = true)] + public string Icon { get; set; } + + /// + /// Returns or sets the package's readme file (md format). + /// + [XmlElement("readme", IsNullable = true)] + public string Readme { get; set; } + + /// + /// Returns or sets the package's privacy policy file (md format). + /// + [XmlElement("privacypolicy", IsNullable = true)] + public string PrivacyPolicy { get; set; } + + /// + /// Returns or sets the description. + /// + [XmlElement("description", IsNullable = true)] + public string Description { get; set; } + + /// + /// Returns or sets the tags. + /// + [XmlElement("tags", IsNullable = true)] + public string Tags { get; set; } + + /// + /// Returns or sets the path to the plugins. + /// + [XmlElement("plugin", IsNullable = true)] + public string[] Plugins { get; set; } + + /// + /// Returns or sets the artifacts. + /// + [XmlElement("artifact", IsNullable = true)] + public string[] Artifacts { get; set; } + } +} diff --git a/src/WebExpress/WebPackage/PackageManager.cs b/src/WebExpress/WebPackage/PackageManager.cs new file mode 100644 index 0000000..79ba632 --- /dev/null +++ b/src/WebExpress/WebPackage/PackageManager.cs @@ -0,0 +1,480 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Reflection; +using System.Runtime.Versioning; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Xml; +using System.Xml.Serialization; +using WebExpress.Internationalization; +using WebExpress.WebComponent; +using WebExpress.WebPlugin; + +namespace WebExpress.WebPackage +{ + /// + /// The package manager manages packages with WebExpress extensions. The packages must be in WebExpressPackage format (*.wxp). + /// + public sealed class PackageManager : IComponent, ISystemComponent + { + /// + /// An event that fires when an package is added. + /// + public static 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; } + + /// + /// Thread Termination. + /// + private CancellationTokenSource TokenSource { get; } = new CancellationTokenSource(); + + /// + /// Returns the catalog of installed packages. + /// + private PackageCatalog Catalog { get; } = new PackageCatalog(); + + /// + /// Constructor + /// + internal PackageManager() + { + + } + + /// + /// Initialization + /// + /// The reference to the context of the host. + public void Initialization(IHttpServerContext context) + { + HttpServerContext = context; + + HttpServerContext.Log.Debug + ( + InternationalizationManager.I18N("webexpress:packagemanager.initialization") + ); + } + + /// + /// Starts the manager. + /// + internal void Execute() + { + // load the default plugins + ComponentManager.PluginManager.Register(); + + // boot default elements + ComponentManager.BootComponent(ComponentManager.PluginManager.Plugins); + + LoadCatalog(); + + foreach (var package in Catalog.Packages) + { + var packagesFromFile = LoadPackage(Path.Combine(HttpServerContext.PackagePath, package.File)); + + package.Metadata = packagesFromFile?.Metadata; + + HttpServerContext.Log.Debug + ( + InternationalizationManager.I18N("webexpress:packagemanager.existing", package.File) + ); + + if (package.State != PackageCatalogeItemState.Disable) + { + package.State = PackageCatalogeItemState.Active; + ExtractPackage(package); + RegisterPackage(package); + BootPackage(package); + } + } + + SaveCatalog(); + + // build sitemap + ComponentManager.SitemapManager.Refresh(); + + Task.Factory.StartNew(() => + { + while (!TokenSource.IsCancellationRequested) + { + Scan(); + + var secendsLeft = 60 - DateTime.Now.Second; + Thread.Sleep(secendsLeft * 1000); + } + + }, TokenSource.Token); + } + + /// + /// Stop running the manager. + /// + public void ShutDown() + { + TokenSource.Cancel(); + } + + /// + /// Searches the package directory for new, changed or removed packages. + /// + public void Scan() + { + HttpServerContext.Log.Debug + ( + InternationalizationManager.I18N + ( + "webexpress: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(); + + // all packages that are not yet installed + var newPackages = packageFiles.Except(Catalog.Packages.Where(x => x != null).Select(x => x.File)).ToList(); + + // all packages that are already installed + //var existingPackages = Catalog.Packages.Select(x => x.File); + + // all packages that are no longer available + var removePackages = Catalog.Packages.Where(x => x != null).Select(x => x.File).Except(packageFiles).ToList(); + + foreach (var package in newPackages) + { + var packagesFromFile = LoadPackage(Path.Combine(HttpServerContext.PackagePath, package)); + + ExtractPackage(packagesFromFile); + RegisterPackage(packagesFromFile); + BootPackage(packagesFromFile); + + Catalog.Packages.Add(packagesFromFile); + + HttpServerContext.Log.Debug + ( + InternationalizationManager.I18N + ( + "webexpress:packagemanager.add", + package + ) + ); + } + + foreach (var package in removePackages) + { + var packagesFromFile = LoadPackage(Path.Combine(HttpServerContext.PackagePath, package)); + + Catalog.Packages.Add(packagesFromFile); + + HttpServerContext.Log.Debug + ( + InternationalizationManager.I18N + ( + "webexpress:packagemanager.remove", + package + ) + ); + } + + // 2. alle WebExpress-Pakete aus dem Filesystem ermitteln + + + // 2. Installiere Pakete ermitteln + var packagesFromCatalog = Catalog.Packages; + + + + // 2 . DLL extrahieren + //webExpressPackages.ForEach(x => x.Files); + + + //foreach () + //{ + // if (!Packages.ContainsKey(packagefile)) + // { + // var package = Package.Open(packagefile); + // Console.WriteLine("Nuspec File: " + package.FileName); + // Console.WriteLine("Nuspec Id: " + package.Id); + // Console.WriteLine("Nuspec Version: " + package.Version); + // Console.WriteLine("Nuspec Autoren: " + package.Authors); + // Console.WriteLine("Nuspec License: " + package.License); + // Console.WriteLine("Nuspec LicenseUrl: " + package.LicenseUrl); + // Console.WriteLine("Nuspec Description: " + package.Description); + // Console.WriteLine("Nuspec Repository: " + package.Repository); + // Console.WriteLine("Nuspec Abhängigkeiten: " + string.Join(",", package.Dependencies.Select(x => x.Id))); + + // Packages.Add(packagefile, package); + // } + //} + + if (newPackages.Any() || removePackages.Any()) + { + // build sitemap + ComponentManager.SitemapManager.Refresh(); + + // save the catalog + SaveCatalog(); + } + } + + /// + /// Opens a package and finds the meta information. + /// + /// The path and file name. + /// The package information as a catalog entry. + private PackageCatalogItem LoadPackage(string file) + { + try + { + if (File.Exists(file)) + { + using var zip = ZipFile.Open(file, ZipArchiveMode.Read); + + var specEntry = zip.Entries.Where(x => Path.GetExtension(x.FullName) == ".spec").FirstOrDefault(); + var serializer = new XmlSerializer(typeof(PackageItemSpec)); + var spec = (PackageItemSpec)serializer.Deserialize(specEntry.Open()); + // var files = new List>(); + + // foreach (ZipArchiveEntry entry in zip.Entries.Where(x => Path.GetDirectoryName(x.FullName).StartsWith("lib"))) + // { + // Console.WriteLine("Lib: " + entry?.FullName); + + // using var stream = entry?.Open(); + // using MemoryStream ms = new MemoryStream(); + // stream.CopyTo(ms); + // files.Add(new Tuple(entry?.FullName, ms.ToArray())); + // } + + // foreach (ZipArchiveEntry entry in zip.Entries.Where(x => Path.GetDirectoryName(x.FullName).StartsWith("runtimes"))) + // { + // Console.WriteLine("Runtimes: " + entry?.FullName); + + // using var stream = entry?.Open(); + // using MemoryStream ms = new MemoryStream(); + // stream.CopyTo(ms); + // files.Add(new Tuple(entry?.FullName, ms.ToArray())); + // } + + return new PackageCatalogItem() + { + Id = spec.Id, + File = Path.GetFileName(file), + State = PackageCatalogeItemState.Available, + Metadata = new PackageItem() + { + FileName = Path.GetFileName(file), + Id = spec.Id, + Version = spec.Version, + Title = spec.Title, + Authors = spec.Authors, + License = spec.License, + Icon = spec.Icon, + Readme = spec.Readme, + Description = spec.Description, + Tags = spec.Tags, + PluginSources = spec.Plugins + } + }; + } + } + catch (Exception ex) + { + HttpServerContext.Log.Exception(ex); + } + + HttpServerContext.Log.Debug + ( + InternationalizationManager.I18N + ( + "webexpress:packagemanager.packagenotfound", + file + ) + ); + + return null; + } + + /// + /// Load the catalog. + /// + private void LoadCatalog() + { + var catalogeFile = Path.Combine(HttpServerContext.PackagePath, "catalog.xml"); + if (File.Exists(catalogeFile)) + { + using var catalog = new StreamReader(catalogeFile); + var serializer = new XmlSerializer(typeof(PackageCatalog)); + var items = (PackageCatalog)serializer.Deserialize(catalog); + + Catalog.Packages.Clear(); + //Catalog.Packages.RemoveAll(x => !x.System); + Catalog.Packages.AddRange(items.Packages); + } + } + + /// + /// Save the catalog. + /// + private void SaveCatalog() + { + 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("", "") })); + + HttpServerContext.Log.Debug + ( + InternationalizationManager.I18N("webexpress:packagemanager.save") + ); + } + + /// + /// Extracts the specified package to the file system. + /// + /// The package. + private void ExtractPackage(PackageCatalogItem package) + { + 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)); + + if (!Directory.Exists(extractedPath)) + { + Directory.CreateDirectory(extractedPath); + // Bestehendes Verzeichnis löschen + //Directory.Delete(extractedPath, true); + } + + foreach (var entry in zip.Entries.Where(x => Path.GetDirectoryName(x.FullName).StartsWith("lib"))) + { + var entryFileName = Path.Combine(extractedPath, entry?.FullName); + + if (entryFileName.EndsWith("/")) + { + if (!Directory.Exists(entryFileName)) + { + Directory.CreateDirectory(entryFileName); + } + } + else + { + if (!Directory.Exists(Path.GetDirectoryName(entryFileName))) + { + Directory.CreateDirectory(Path.GetDirectoryName(entryFileName)); + } + + if (!File.Exists(entryFileName)) + { + entry.ExtractToFile(entryFileName); + } + } + } + } + } + + /// + /// Registers the plungins included in the package. + /// + /// The package. + private void RegisterPackage(PackageCatalogItem package) + { + // load plugins + foreach (var plugin in package?.Metadata.PluginSources ?? Enumerable.Empty()) + { + var pluginContexts = ComponentManager.PluginManager.Register(GetTargetPath(package, plugin)); + + package.Plugins.AddRange(pluginContexts); + } + + ComponentManager.LogStatus(); + } + + /// + /// Boots the components included in the package. + /// + /// The package. + private void BootPackage(PackageCatalogItem package) + { + ComponentManager.BootComponent(package.Plugins); + } + + /// + /// Determines the target directory where the plug-ins of the package are located for the current target platform + /// + /// The package. + /// The plugin. + /// The directory (absolutely). + private string GetTargetPath(PackageCatalogItem package, string plugin) + { + return Path.GetFullPath(Path.Combine + ( + HttpServerContext.PackagePath, + Path.GetFileNameWithoutExtension(package?.File), plugin, GetTFM(), $"{Path.GetFileName(plugin)}.dll" + )); + } + + /// + /// Determines the target framework. + /// + /// The TFM + private string GetTFM() + { + var targetFrameworkAttribute = Assembly.GetExecutingAssembly() + .GetCustomAttributes(typeof(TargetFrameworkAttribute), false) + .Select(x => x as TargetFrameworkAttribute) + .SingleOrDefault(); + + return targetFrameworkAttribute.FrameworkDisplayName.Replace(" ", "").ToLower().Replace(".net", "net"); + } + + /// + /// Raises the AddPackage event. + /// + /// The package catalog item. + private void OnAddPackage(PackageCatalogItem item) + { + AddPackage?.Invoke(this, item); + } + + /// + /// Raises the RemovePackage event. + /// + /// The package catalog item. + private void OnRemovePackage(PackageCatalogItem item) + { + RemovePackage?.Invoke(this, 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) + { + } + } +} diff --git a/src/WebExpress/WebPage/IPage.cs b/src/WebExpress/WebPage/IPage.cs new file mode 100644 index 0000000..3fc5e9d --- /dev/null +++ b/src/WebExpress/WebPage/IPage.cs @@ -0,0 +1,19 @@ +using WebExpress.WebResource; + +namespace WebExpress.WebPage +{ + public interface IPage : IResource + { + /// + /// Returns or sets the page title. + /// + string Title { get; set; } + + /// + /// Redirect to another page. + /// The function throws the RedirectException. + /// + /// The uri to redirect to. + void Redirecting(string uri); + } +} diff --git a/src/WebExpress/WebPage/IVisualTree.cs b/src/WebExpress/WebPage/IVisualTree.cs new file mode 100644 index 0000000..2e95727 --- /dev/null +++ b/src/WebExpress/WebPage/IVisualTree.cs @@ -0,0 +1,74 @@ +using System.Collections.Generic; +using WebExpress.WebHtml; + +namespace WebExpress.WebPage +{ + public interface IVisualTree + { + /// + /// Liefert oder setzt das Favicon + /// + List Favicons { get; } + + /// + /// Liefert oder setzt das internes Stylesheet + /// + List Styles { get; } + + /// + /// Liefert oder setzt die Links auf die zu verwendenden JavaScript-Dateien, welche im Header eingefügt werden + /// + List HeaderScriptLinks { get; } + + /// + /// Liefert oder setzt die Links auf die zu verwendenden JavaScript-Dateien + /// + List ScriptLinks { get; } + + /// + /// Liefert oder setzt die Links auf die zu verwendenden JavaScript-Dateien, welche im Header eingefügt werden + /// + List HeaderScripts { get; } + + /// + /// Liefert oder setzt die Links auf die zu verwendenden JavaScript-Dateien + /// + IDictionary Scripts { get; } + + /// + /// Liefert oder setzt die Links auf die zu verwendenden Css-Dateien + /// + List CssLinks { get; } + + /// + /// Liefert oder setzt die Metainformationen + /// + List> Meta { get; } + + /// + /// Fügt eine Java-Script hinzu + /// + /// Der Link + void AddScriptLink(string url); + + /// + /// Fügt eine Java-Script im Header hinzu + /// + /// Der Link + void AddHeaderScriptLinks(string url); + + /// + /// Fügt eine Java-Script hinzu oder sersetzt dieses, falls vorhanden + /// + /// Der Schlüssel + /// Der Code + void AddScript(string key, string code); + + /// + /// Convert to html. + /// + /// The context for rendering the page. + /// Die Seite als HTML + IHtmlNode Render(RenderContext context); + } +} diff --git a/src/WebExpress/WebPage/Page.cs b/src/WebExpress/WebPage/Page.cs new file mode 100644 index 0000000..447a999 --- /dev/null +++ b/src/WebExpress/WebPage/Page.cs @@ -0,0 +1,71 @@ +using WebExpress.WebMessage; +using WebExpress.WebResource; + +namespace WebExpress.WebPage +{ + /// + /// The prototype of a website. + /// + /// An implementation of the visualization tree. + public abstract class Page : Resource, IPage where T : RenderContext, new() + { + /// + /// Returns or sets the page title. + /// + public string Title { get; set; } + + /// + /// Constructor + /// + public Page() + { + + } + + /// + /// Initialization + /// + /// The context of the resource. + public override void Initialization(IResourceContext context) + { + base.Initialization(context); + } + + /// + /// Redirect to another page. + /// The function throws the RedirectException. + /// + /// The uri to redirect to. + public void Redirecting(string uri) + { + throw new RedirectException(uri?.ToString()); + } + + /// + /// Processing of the resource. + /// + /// 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) + }; + } + + /// + /// Processing of the resource. + /// + /// The context for rendering the page. + public abstract void Process(T context); + } +} diff --git a/src/WebExpress/WebPage/RenderContext.cs b/src/WebExpress/WebPage/RenderContext.cs new file mode 100644 index 0000000..27bb5d1 --- /dev/null +++ b/src/WebExpress/WebPage/RenderContext.cs @@ -0,0 +1,91 @@ +using System.Globalization; +using WebExpress.Internationalization; +using WebExpress.WebApplication; +using WebExpress.WebMessage; +using WebExpress.WebResource; +using WebExpress.WebUri; + +namespace WebExpress.WebPage +{ + public class RenderContext : II18N + { + /// + /// The page where the control is rendered. + /// + public IPage Page { get; internal set; } + + /// + /// Returns the request. + /// + public Request Request { get; internal set; } + + /// + /// Returns the host context. + /// + public IHttpServerContext Host => Request.ServerContext; + + /// + /// The uri of the request. + /// + public UriResource Uri => Request.Uri; + + /// + /// Returns the context path. + /// + public UriResource ContextPath => Page?.ResourceContext?.ContextPath; + + /// + /// 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; } + + /// + /// Returns the log for writing status messages to the console and to a log file. + /// + public Log Log { get; private set; } + + /// + /// Constructor + /// + public RenderContext() + { + } + + /// + /// Constructor + /// + /// The page where the control is rendered. + /// The request. + /// The visual tree. + public RenderContext(IPage page, Request request, IVisualTree visualTree) + { + Page = page; + Request = request; + VisualTree = visualTree; + Culture = (Page as Resource).Culture; + } + + /// + /// Copy-Constructor + /// + /// The context to copy./param> + public RenderContext(RenderContext context) + : this(context?.Page, context?.Request, context?.VisualTree) + { + } + } +} diff --git a/src/WebExpress/WebPage/VisualTree.cs b/src/WebExpress/WebPage/VisualTree.cs new file mode 100644 index 0000000..12cb9ba --- /dev/null +++ b/src/WebExpress/WebPage/VisualTree.cs @@ -0,0 +1,129 @@ +using System.Collections.Generic; +using System.Linq; +using WebExpress.WebHtml; +using WebExpress.Internationalization; +using WebExpress.WebPage; + +namespace WebExpress.WebResource +{ + /// + /// Die inhaltliche Gestanltung einer Seite (Page) wird durch den visuellen Baum bestimmt. + /// + public abstract class VisualTree : IVisualTree + { + /// + /// Liefert die Favicons + /// + public List Favicons { get; } = new List(); + + /// + /// Liefert die internen Stylesheets + /// + public List Styles { get; } = new List(); + + /// + /// Liefert die Links auf die zu verwendenden JavaScript-Dateien, welche im Header eingefügt werden + /// + public List HeaderScriptLinks { get; } = new List(); + + /// + /// Liefert die Links auf die zu verwendenden JavaScript-Dateien + /// + public List ScriptLinks { get; } = new List(); + + /// + /// Liefert die Links auf die zu verwendenden JavaScript-Dateien, welche im Header eingefügt werden + /// + public List HeaderScripts { get; } = new List(); + + /// + /// Liefert die Links auf die zu verwendenden JavaScript-Dateien + /// + public IDictionary Scripts { get; } = new Dictionary(); + + /// + /// Liefert die Links auf die zu verwendenden Css-Dateien + /// + public List CssLinks { get; } = new List(); + + /// + /// Liefert die Metainformationen + /// + public List> Meta { get; } = new List>(); + + /// + /// Returns the content. + /// + public IHtmlNode Content { get; } + + /// + /// Constructor + /// + public VisualTree() + { + } + + /// + /// Fügt eine Java-Script hinzu oder sersetzt dieses, falls vorhanden + /// + /// Der Schlüssel + /// Der Code + public virtual void AddScript(string key, string code) + { + if (key == null) return; + + var k = key.ToLower(); + var dict = Scripts; + + if (dict.ContainsKey(k)) + { + dict[k] = code; + } + else + { + dict?.Add(k, code); + } + } + + /// + /// Fügt eine Java-Script hinzu + /// + /// Der Link + public virtual void AddScriptLink(string url) + { + ScriptLinks?.Add(url); + } + + /// + /// Fügt eine Java-Script im Header hinzu + /// + /// Der Link + public virtual void AddHeaderScriptLinks(string url) + { + HeaderScriptLinks?.Add(url); + } + + /// + /// Convert to html. + /// + /// The context for rendering the page. + /// The page as an html tree. + public virtual IHtmlNode Render(RenderContext context) + { + var html = new HtmlElementRootHtml(); + html.Head.Title = InternationalizationManager.I18N(context.Request, context.Page?.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.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/WebPlugin/IPlugin.cs b/src/WebExpress/WebPlugin/IPlugin.cs new file mode 100644 index 0000000..3e39420 --- /dev/null +++ b/src/WebExpress/WebPlugin/IPlugin.cs @@ -0,0 +1,21 @@ +using System; + +namespace WebExpress.WebPlugin +{ + /// + /// This interface represents a plugin. + /// + public interface IPlugin : IDisposable + { + /// + /// Initialization of the plugin. + /// + /// The context. + void Initialization(IPluginContext context); + + /// + /// Called when the plugin starts working. The call is concurrent. + /// + void Run(); + } +} diff --git a/src/WebExpress/WebPlugin/IPluginContext.cs b/src/WebExpress/WebPlugin/IPluginContext.cs new file mode 100644 index 0000000..57152f8 --- /dev/null +++ b/src/WebExpress/WebPlugin/IPluginContext.cs @@ -0,0 +1,61 @@ +using System.Reflection; +using WebExpress.WebUri; + +namespace WebExpress.WebPlugin +{ + /// + /// The context of a plugin. + /// + public interface IPluginContext + { + /// + /// The assembly that contains the plugin. + /// + Assembly Assembly { get; } + + /// + /// Returns the plugin id. + /// + string PluginId { get; } + + /// + /// Returns the name of the plugin. + /// + string PluginName { get; } + + /// + /// Returns the manufacturer of the plugin. + /// + string Manufacturer { get; } + + /// + /// Returns the description of the plugin. + /// + string Description { get; } + + /// + /// Returns the version of the plugin. + /// + string Version { get; } + + /// + /// Returns the copyright information. + /// + string Copyright { get; } + + /// + /// Returns the license information. + /// + string License { get; } + + /// + /// Returns the icon of the plugin. + /// + UriResource Icon { get; } + + /// + /// Returns the host context. + /// + IHttpServerContext Host { get; } + } +} diff --git a/src/WebExpress/WebPlugin/PluginContext.cs b/src/WebExpress/WebPlugin/PluginContext.cs new file mode 100644 index 0000000..35b8965 --- /dev/null +++ b/src/WebExpress/WebPlugin/PluginContext.cs @@ -0,0 +1,74 @@ +using System.Reflection; +using WebExpress.WebUri; + +namespace WebExpress.WebPlugin +{ + public class PluginContext : IPluginContext + { + /// + /// The assembly that contains the plugin. + /// + public Assembly Assembly { get; internal set; } + + /// + /// Returns the plugin id. + /// + public string PluginId { get; internal set; } + + /// + /// Returns the name of the plugin. + /// + public string PluginName { get; internal set; } + + /// + /// Returns the manufacturer of the plugin. + /// + public string Manufacturer { get; internal set; } + + /// + /// Returns the copyright information. + /// + public string Copyright { get; internal set; } + + /// + /// Returns the description of the plugin. + /// + public string Description { get; internal set; } + + /// + /// Returns the version of the plugin. + /// + public string Version { get; internal set; } + + /// + /// Returns the license information. + /// + public string License { get; internal set; } + + /// + /// Returns the icon of the plugin. + /// + public UriResource Icon { get; internal set; } + + /// + /// Returns the host context. + /// + public IHttpServerContext Host { get; internal set; } + + /// + /// Constructor + /// + public PluginContext() + { + } + + /// + /// Conversion of the plugin context into its string representation. + /// + /// The string that uniquely represents the plugin. + public override string ToString() + { + return PluginId; + } + } +} diff --git a/src/WebExpress/WebPlugin/PluginDictionary.cs b/src/WebExpress/WebPlugin/PluginDictionary.cs new file mode 100644 index 0000000..35741ca --- /dev/null +++ b/src/WebExpress/WebPlugin/PluginDictionary.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; + +namespace WebExpress.WebPlugin +{ + /// + /// Verzeichnis über die registrieten Plugins + /// Key = PluginId + /// Value = Plugin-Metadaten + /// + internal class PluginDictionary : Dictionary + { + } +} diff --git a/src/WebExpress/WebPlugin/PluginItem.cs b/src/WebExpress/WebPlugin/PluginItem.cs new file mode 100644 index 0000000..79ac382 --- /dev/null +++ b/src/WebExpress/WebPlugin/PluginItem.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Threading; + +namespace WebExpress.WebPlugin +{ + /// + /// Represents a plugin entry. + /// + internal class PluginItem + { + /// + /// The plugin load context for isolating and unloading the dependent libraries. + /// + public PluginLoadContext PluginLoadContext { get; internal set; } + + /// + /// Returns the plugin class. + /// + public Type PluginClass { get; internal set; } + + /// + /// The context associated with the plugin. + /// + public IPluginContext PluginContext { get; internal set; } + + /// + /// The plugin. + /// + public IPlugin Plugin { get; internal set; } + + /// + /// The dependencies of the plugin. + /// + public IEnumerable Dependencies { get; internal set; } = new List(); + + /// + /// Thread termination token. + /// + public CancellationTokenSource CancellationTokenSource { get; } = new CancellationTokenSource(); + } +} diff --git a/src/WebExpress/WebPlugin/PluginLoadContext.cs b/src/WebExpress/WebPlugin/PluginLoadContext.cs new file mode 100644 index 0000000..e03b3fd --- /dev/null +++ b/src/WebExpress/WebPlugin/PluginLoadContext.cs @@ -0,0 +1,61 @@ +using System; +using System.Reflection; +using System.Runtime.Loader; + +namespace WebExpress.WebPlugin +{ + /// + /// Isolation of plug-in dependencies. + /// see https://github.com/dotnet/samples/tree/main/core/extensions/AppWithPlugin + /// + public class PluginLoadContext : AssemblyLoadContext + { + /// + /// Returns or set the resolver to resolve dependencies. + /// + private AssemblyDependencyResolver Resolver { get; set; } + + /// + /// Constructor + /// + /// The base path of the plugin. + public PluginLoadContext(string pluginPath) + { + Resolver = new AssemblyDependencyResolver(pluginPath); + } + + /// + /// Allows an assembly to be resolved based on its AssemblyName. + /// + /// The object that describes the assembly to be resolved. + /// The resolved assembly, or null. + protected override Assembly Load(AssemblyName assemblyName) + { + string assemblyPath = Resolver.ResolveAssemblyToPath(assemblyName); + + if (assemblyPath != null) + { + return LoadFromAssemblyPath(assemblyPath); + } + + return null; + } + + /// + /// Allows to load an unmanaged library by name. + /// + /// 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) + { + string libraryPath = Resolver.ResolveUnmanagedDllToPath(unmanagedDllName); + + if (libraryPath != null) + { + return LoadUnmanagedDllFromPath(libraryPath); + } + + return IntPtr.Zero; + } + } +} diff --git a/src/WebExpress/WebPlugin/PluginManager.cs b/src/WebExpress/WebPlugin/PluginManager.cs new file mode 100644 index 0000000..38f8bea --- /dev/null +++ b/src/WebExpress/WebPlugin/PluginManager.cs @@ -0,0 +1,582 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Threading.Tasks; +using WebExpress.Internationalization; +using WebExpress.WebAttribute; +using WebExpress.WebComponent; +using WebExpress.WebUri; + +namespace WebExpress.WebPlugin +{ + /// + /// The plugin manager manages the WebExpress plugins. + /// + public class PluginManager : IComponent, IExecutableElements, ISystemComponent + { + /// + /// An event that fires when an plugin is added. + /// + public event EventHandler AddPlugin; + + /// + /// An event that fires when an plugin is removed. + /// + 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(); + + /// + /// Constructor + /// + internal PluginManager() + { + ComponentManager.AddComponent += (s, e) => + { + //AssignToComponent(e); + }; + + ComponentManager.RemoveComponent += (s, e) => + { + //DetachFromcomponent(e); + }; + } + + /// + /// Initialization + /// + /// The reference to the context of the host. + public void Initialization(IHttpServerContext context) + { + HttpServerContext = context; + + HttpServerContext.Log.Debug + ( + InternationalizationManager.I18N("webexpress:pluginmanager.initialization") + ); + } + + /// + /// Loads and registers the plugins that are static (i.e. located in the application's folder). + /// 1 + /// A list of plugins created. + internal void Register() + { + var path = Environment.CurrentDirectory; + var assemblies = new List(); + + // create plugins + foreach (var assemblyFile in Directory.EnumerateFiles(path, "*.dll", SearchOption.TopDirectoryOnly)) + { + try + { + var assembly = Assembly.LoadFrom(assemblyFile); + if (assembly != null) + { + assemblies.Add(assembly); + HttpServerContext.Log.Debug + ( + InternationalizationManager.I18N + ( + "webexpress:pluginmanager.load", + assembly.GetName().Name, + assembly.GetName().Version.ToString() + ) + ); + } + } + catch (BadImageFormatException) + { + + } + } + + // register plugin + foreach (var assembly in assemblies + .OrderBy(x => x.GetCustomAttribute(typeof(SystemPluginAttribute)) != null ? 0 : 1)) + { + Register(assembly); + } + + Logging(); + } + + /// + /// Loads and registers the plugins from a path. + /// + /// The directory and filename where the plugins are located. + internal IEnumerable Register(string pluginFile) + { + var assemblies = new List(); + var pluginContexts = new List(); + + if (!File.Exists(pluginFile)) + { + return pluginContexts; + } + + var loadContext = new PluginLoadContext(pluginFile); + + // create plugins + try + { + var assembly = loadContext.LoadFromAssemblyName(AssemblyName.GetAssemblyName(pluginFile)); + + if (assembly != null) + { + assemblies.Add(assembly); + HttpServerContext.Log.Debug + ( + InternationalizationManager.I18N + ( + "webexpress:pluginmanager.load", + assembly.GetName().Name, + assembly.GetName().Version.ToString() + ) + ); + } + } + catch (BadImageFormatException) + { + + } + + // register plugin + foreach (var assembly in assemblies) + { + var pluginContext = Register(assembly, loadContext); + pluginContexts.Add(pluginContext); + } + + Logging(); + + return pluginContexts; + } + + /// + /// Loads and registers the plugin from an assembly. + /// + /// 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) + { + try + { + 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"))) + { + var id = type.Namespace?.ToLower(); + var name = type.Assembly.GetCustomAttribute()?.Title; + var icon = string.Empty; + var description = type.Assembly.GetCustomAttribute()?.Description; + var dependencies = new List(); + var hasUnfulfilledDependencies = false; + + foreach (var customAttribute in type.CustomAttributes + .Where(x => x.AttributeType.GetInterfaces().Contains(typeof(IPluginAttribute)))) + { + 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(DependencyAttribute)) + { + dependencies.Add(customAttribute.ConstructorArguments.FirstOrDefault().Value?.ToString()); + } + } + + var pluginContext = new PluginContext() + { + Assembly = type.Assembly, + PluginId = id, + PluginName = name, + Manufacturer = type.Assembly.GetCustomAttribute()?.Company, + Copyright = type.Assembly.GetCustomAttribute()?.Copyright, + //License = type.Assembly.GetCustomAttribute()?.Copyright, + Icon = UriResource.Combine(HttpServerContext.ContextPath, icon), + Description = description, + Version = type.Assembly.GetCustomAttribute()?.InformationalVersion, + Host = HttpServerContext + }; + + hasUnfulfilledDependencies = HasUnfulfilledDependencies(id, dependencies); + + if (hasUnfulfilledDependencies) + { + UnfulfilledDependencies.Add(id, new PluginItem() + { + PluginLoadContext = loadContext, + PluginClass = type, + PluginContext = pluginContext, + Plugin = Activator.CreateInstance(type) as IPlugin, + Dependencies = dependencies + }); + } + else if (!Dictionary.ContainsKey(id)) + { + Dictionary.Add(id, new PluginItem() + { + PluginLoadContext = loadContext, + PluginClass = type, + PluginContext = pluginContext, + Plugin = Activator.CreateInstance(type) as IPlugin, + Dependencies = dependencies + }); + + HttpServerContext.Log.Debug + ( + InternationalizationManager.I18N("webexpress:pluginmanager.created", id) + ); + + OnAddPlugin(pluginContext); + + CheckUnfulfilledDependencies(); + } + else + { + HttpServerContext.Log.Warning + ( + InternationalizationManager.I18N("webexpress:pluginmanager.duplicate", id) + ); + } + + return pluginContext; + } + } + catch (Exception ex) + { + HttpServerContext.Log.Exception(ex); + } + + return null; + } + + /// + /// 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) + { + OnRemovePlugin(pluginContext); + + var pluginItem = GetPluginItem(pluginContext); + pluginItem?.PluginLoadContext?.Unload(); + } + + /// + /// Check if dependencies of other plugins are now fulfilled after a plugin has been added. + /// + private void CheckUnfulfilledDependencies() + { + bool fulfilledDependencies; + + do + { + fulfilledDependencies = false; + + foreach (var unfulfilledDependencies in UnfulfilledDependencies) + { + var hasUnfulfilledDependencies = HasUnfulfilledDependencies + ( + unfulfilledDependencies.Key, + unfulfilledDependencies.Value.Dependencies + ); + + if (!hasUnfulfilledDependencies) + { + fulfilledDependencies = true; + UnfulfilledDependencies.Remove(unfulfilledDependencies.Key); + Dictionary.Add(unfulfilledDependencies.Key, unfulfilledDependencies.Value); + + OnAddPlugin(unfulfilledDependencies.Value.PluginContext); + + HttpServerContext.Log.Debug + ( + InternationalizationManager.I18N + ( + "webexpress:pluginmanager.fulfilleddependencies", + unfulfilledDependencies.Key + ) + ); + } + } + } while (fulfilledDependencies); + } + + /// + /// Checks if there are any unfulfilled dependencies. + /// + /// The id of the plugin. + /// The dependencies to check. + /// True if dependencies exist, false otherwise + private bool HasUnfulfilledDependencies(string id, IEnumerable dependencies) + { + var hasUnfulfilledDependencies = false; + + foreach (var dependency in dependencies + .Where(x => !Dictionary.ContainsKey(x.ToLower()))) + { + // dependency was not fulfilled + hasUnfulfilledDependencies = true; + + HttpServerContext.Log.Debug + ( + InternationalizationManager.I18N + ( + "webexpress:pluginmanager.unfulfilleddependencies", + id, + dependency + ) + ); + } + + return hasUnfulfilledDependencies; + } + + /// + /// Returns a plugin context based on its id. + /// + /// The id of the plugin. + /// The plugin context. + public IPluginContext GetPlugin(string id) + { + return Dictionary.Values + .Where + ( + x => x.PluginContext != null && + x.PluginContext.PluginId.Equals(id, StringComparison.OrdinalIgnoreCase) + ) + .Select(x => x.PluginContext) + .FirstOrDefault(); + } + + /// + /// Returns a plugin item based on the context. + /// + /// The context of the plugin. + /// The plugin item or null. + private PluginItem GetPluginItem(IPluginContext pluginContext) + { + var pluginId = pluginContext?.PluginId?.ToLower(); + + if (pluginId == null || !Dictionary.ContainsKey(pluginId)) + { + HttpServerContext.Log.Warning + ( + InternationalizationManager.I18N + ( + "webexpress:pluginmanager.notavailable", + pluginId + ) + ); + + return null; + } + + return Dictionary[pluginId]; + } + + /// + /// Boots the specified plugin. + /// + /// The context of the plugin to run. + internal void Boot(IPluginContext pluginContext) + { + var pluginItem = GetPluginItem(pluginContext); + var token = pluginItem?.CancellationTokenSource.Token; + + if (pluginItem == null) + { + return; + } + + // initialize plugin + pluginItem.Plugin.Initialization(pluginItem.PluginContext); + HttpServerContext.Log.Debug + ( + InternationalizationManager.I18N + ( + "webexpress:pluginmanager.plugin.initialization", + pluginItem.PluginContext.PluginId + ) + ); + + // run plugin concurrently + Task.Run(() => + { + HttpServerContext.Log.Debug + ( + InternationalizationManager.I18N + ( + "webexpress:pluginmanager.plugin.processing.start", + pluginItem.PluginContext.PluginId + ) + ); + + pluginItem.Plugin.Run(); + + HttpServerContext.Log.Debug + ( + InternationalizationManager.I18N + ( + "webexpress:pluginmanager.plugin.processing.end", + pluginItem.PluginContext.PluginId + ) + ); + + token?.ThrowIfCancellationRequested(); + }, token.Value); + } + + /// + /// Boots the specified plugins. + /// + /// A list with the contexts of the plugins to run. + internal void Boot(IEnumerable contexts) + { + foreach (var context in contexts) + { + Boot(context); + } + } + + /// + /// Shut down the plugin. + /// + /// The context of the plugin to shut down. + public void ShutDown(IPluginContext pluginContext) + { + var plugin = GetPluginItem(pluginContext); + + plugin?.CancellationTokenSource.Cancel(); + + } + + /// + /// Shut down the plugins. + /// + /// A list of contexts of plugins to shut down. + public void ShutDown(IEnumerable contexts) + { + foreach (var context in contexts) + { + ShutDown(context); + } + } + + /// + /// Raises the AddPlugin event. + /// + /// The plugin context. + private void OnAddPlugin(IPluginContext pluginContext) + { + AddPlugin?.Invoke(this, pluginContext); + } + + /// + /// Raises the RemovePlugin event. + /// + /// The plugin context. + private void OnRemovePlugin(IPluginContext pluginContext) + { + RemovePlugin?.Invoke(this, pluginContext); + } + + /// + /// Output of the loaded plugins to the log. + /// + private void Logging() + { + using var frame = new LogFrameSimple(HttpServerContext.Log); + var list = new List(); + HttpServerContext.Log.Info + ( + InternationalizationManager.I18N + ( + "webexpress:pluginmanager.pluginmanager.label" + ) + ); + + list.AddRange(Dictionary + .Where + ( + x => x.Value.PluginClass.Assembly + .GetCustomAttribute(typeof(SystemPluginAttribute)) != null + ) + .Select(x => InternationalizationManager.I18N + ( + "webexpress:pluginmanager.pluginmanager.system", + x.Key + )) + ); + + list.AddRange(Dictionary + .Where + ( + x => x.Value.PluginClass.Assembly + .GetCustomAttribute(typeof(SystemPluginAttribute)) == null + ) + .Select(x => InternationalizationManager.I18N + ( + "webexpress:pluginmanager.pluginmanager.custom", + x.Key + )) + ); + + list.AddRange(UnfulfilledDependencies + .Select(x => InternationalizationManager.I18N + ( + "webexpress:pluginmanager.pluginmanager.unfulfilleddependencies", + x.Key + )) + ); + + foreach (var item in list) + { + HttpServerContext.Log.Info(string.Join(Environment.NewLine, 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) + { + } + } +} diff --git a/src/WebExpress/WebResource/IResource.cs b/src/WebExpress/WebResource/IResource.cs new file mode 100644 index 0000000..903634a --- /dev/null +++ b/src/WebExpress/WebResource/IResource.cs @@ -0,0 +1,57 @@ +using WebExpress.Internationalization; +using WebExpress.WebApplication; +using WebExpress.WebMessage; +using WebExpress.WebModule; + +namespace WebExpress.WebResource +{ + public interface IResource : II18N + { + /// + /// 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/WebResource/IResourceContext.cs b/src/WebExpress/WebResource/IResourceContext.cs new file mode 100644 index 0000000..c971457 --- /dev/null +++ b/src/WebExpress/WebResource/IResourceContext.cs @@ -0,0 +1,69 @@ +using System.Collections.Generic; +using WebExpress.WebApplication; +using WebExpress.WebCondition; +using WebExpress.WebModule; +using WebExpress.WebPlugin; +using WebExpress.WebUri; + +namespace WebExpress.WebResource +{ + public interface IResourceContext + { + /// + /// 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/WebResource/RedirectException.cs b/src/WebExpress/WebResource/RedirectException.cs new file mode 100644 index 0000000..5b013e3 --- /dev/null +++ b/src/WebExpress/WebResource/RedirectException.cs @@ -0,0 +1,29 @@ +using System; + +namespace WebExpress.WebResource +{ + public class RedirectException : Exception + { + /// + /// Liefert oder setzt das Weiterleitungsziel + /// + public string Url { get; set; } + + /// + /// Bestimmt, ob ein permanete Weiterleitung erfolgen soll + /// + public bool Permanet { get; set; } + + /// + /// Constructor + /// + /// Das Weiterleitungsziel + /// true wenn 301 gesendet werden soll, flase für 302 + public RedirectException(string url, bool permanent = false) + : base("Redirecting to " + url) + { + Url = url; + Permanet = permanent; + } + } +} diff --git a/src/WebExpress/WebResource/Resource.cs b/src/WebExpress/WebResource/Resource.cs new file mode 100644 index 0000000..a7c975a --- /dev/null +++ b/src/WebExpress/WebResource/Resource.cs @@ -0,0 +1,78 @@ +using System.Globalization; +using WebExpress.WebApplication; +using WebExpress.WebMessage; +using WebExpress.WebModule; + +namespace WebExpress.WebResource +{ + 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. + /// + public IResourceContext ResourceContext { get; private set; } + + /// + /// Provides the culture. + /// + public CultureInfo Culture { get; set; } + + /// + /// Constructor + /// + public Resource() + { + } + + /// + /// Initialization + /// + /// 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) + { + return; + } + + /// + /// Processing of the resource. + /// + /// The request. + /// The response. + public abstract Response Process(Request request); + + /// + /// Post-processing of the resource. + /// + /// The request. + /// The response. + /// The response. + public virtual Response PostProcess(Request request, Response response) + { + return response; + } + } +} diff --git a/src/WebExpress/WebResource/ResourceAsset.cs b/src/WebExpress/WebResource/ResourceAsset.cs new file mode 100644 index 0000000..d2ac1ce --- /dev/null +++ b/src/WebExpress/WebResource/ResourceAsset.cs @@ -0,0 +1,156 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using WebExpress.WebMessage; +using static WebExpress.Internationalization.InternationalizationManager; + +namespace WebExpress.WebResource +{ + /// + /// Lieferung einer im Assamby eingebetteten Ressource + /// + public class ResourceAsset : ResourceBinary + { + /// + /// Schutz vor Nebenläufgkeit + /// + private object Gard { get; set; } + + /// + /// Liefert oder setzt das Stammverzeichnis + /// + public string AssetDirectory { get; protected set; } + + /// + /// Constructor + /// + public ResourceAsset() + { + Gard = new object(); + } + + /// + /// Initialization + /// + /// The context. + public override void Initialization(IResourceContext context) + { + base.Initialization(context); + + AssetDirectory = ResourceContext.PluginContext.Assembly.GetName().Name; + } + + /// + /// Processing of the resource. + /// + /// The request. + /// The response. + public override Response Process(Request request) + { + lock (Gard) + { + 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 fileName = Path.GetFileName(url); + var file = string.Join('.', AssetDirectory.Trim('.'), "assets", url.Replace("/", ".").Trim('.')); + + Data = GetData(file, assembly, resources.ToList()); + + if (Data == null) + { + return new ResponseNotFound(); + } + + var response = base.Process(request); + response.Header.CacheControl = "public, max-age=31536000"; + + var extension = Path.GetExtension(fileName); + extension = !string.IsNullOrWhiteSpace(extension) ? extension.ToLower() : ""; + + 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 ".xml": + response.Header.ContentType = "text/xml"; + break; + case ".html": + case ".htm": + response.Header.ContentType = "text/html"; + break; + case ".exe": + response.Header.ContentDisposition = "attatchment; filename=" + fileName + "; size=" + Data.LongLength; + response.Header.ContentType = "application/octet-stream"; + break; + case ".zip": + response.Header.ContentDisposition = "attatchment; filename=" + fileName + "; 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; + } + + request.ServerContext.Log.Debug(I18N("webexpress:resource.file", request.RemoteEndPoint, request.Uri)); + + return response; + } + } + + /// + /// Liest die Daten einer angeegbenen Ressource + /// + /// Die Datei + /// Das Assembly + /// + private static byte[] GetData(string file, Assembly assembly, IEnumerable resources) + { + var item = resources.Where(x => x.Equals(file, System.StringComparison.OrdinalIgnoreCase)).FirstOrDefault(); + if (item == null) + { + return null; + } + + using var stream = assembly.GetManifestResourceStream(item); + using var memoryStream = new MemoryStream(); + stream.CopyTo(memoryStream); + + return memoryStream.ToArray(); + } + } +} \ No newline at end of file diff --git a/src/WebExpress/WebResource/ResourceBinary.cs b/src/WebExpress/WebResource/ResourceBinary.cs new file mode 100644 index 0000000..0a2a611 --- /dev/null +++ b/src/WebExpress/WebResource/ResourceBinary.cs @@ -0,0 +1,38 @@ +using WebExpress.WebMessage; + +namespace WebExpress.WebResource +{ + /// + /// Arbeitet eine Anfrage ab. Dies erfolgt nebenläufig + /// + public abstract class ResourceBinary : Resource + { + /// + /// Returns or sets the data. + /// + public byte[] Data { get; set; } + + /// + /// Constructor + /// + public ResourceBinary() + { + } + + /// + /// Processing of the resource. + /// + /// The request. + /// The response. + public override Response Process(Request request) + { + var response = new ResponseOK(); + response.Header.ContentLength = Data != null ? Data.Length : 0; + response.Header.ContentType = "binary/octet-stream"; + + response.Content = Data; + + return response; + } + } +} diff --git a/src/WebExpress/WebResource/ResourceContext.cs b/src/WebExpress/WebResource/ResourceContext.cs new file mode 100644 index 0000000..fcd82fa --- /dev/null +++ b/src/WebExpress/WebResource/ResourceContext.cs @@ -0,0 +1,103 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using WebExpress.WebApplication; +using WebExpress.WebComponent; +using WebExpress.WebCondition; +using WebExpress.WebModule; +using WebExpress.WebPlugin; +using WebExpress.WebUri; + +namespace WebExpress.WebResource +{ + public class ResourceContext : IResourceContext + { + /// + /// Returns the associated plugin context. + /// + public IPluginContext PluginContext { get; private set; } + + /// + /// Returns the associated application context. + /// + public IApplicationContext ApplicationContext => ModuleContext?.ApplicationContext; + + /// + /// Returns the corresponding module context. + /// + public IModuleContext ModuleContext { get; private 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; } = new List(); + + /// + /// Returns the resource id. + /// + public string ResourceId { get; internal set; } + + /// + /// Returns the resource title. + /// + public string ResourceTitle { get; internal set; } + + /// + /// Returns the parent or null if not used. + /// + 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(); + + /// + /// Returns whether the resource is created once and reused each time it is called. + /// + public bool Cache { get; internal set; } + + /// + /// Returns the context path. + /// + public UriResource ContextPath + { + get + { + var parentContext = ParentContext; + if (parentContext != null) + { + return UriResource.Combine(ParentContext?.Uri, ResourceItem.ContextPath); + } + + return UriResource.Combine(ModuleContext.ContextPath, ResourceItem.ContextPath); + } + } + + /// + /// Returns the uri. + /// + public UriResource Uri => ContextPath.Append(ResourceItem.PathSegment); + + /// + /// Constructor + /// + /// The module context. + internal ResourceContext(IModuleContext moduleContext) + { + PluginContext = moduleContext?.PluginContext; + ModuleContext = moduleContext; + } + + /// + /// Returns or sets the resource item. + /// + internal ResourceItem ResourceItem { get; set; } + } +} diff --git a/src/WebExpress/WebResource/ResourceDictionary.cs b/src/WebExpress/WebResource/ResourceDictionary.cs new file mode 100644 index 0000000..773fb08 --- /dev/null +++ b/src/WebExpress/WebResource/ResourceDictionary.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; +using WebExpress.WebPlugin; + +namespace WebExpress.WebResource +{ + /// + /// key = plugin context + /// value = { key = resource id, value = ressource item } + /// + internal class ResourceDictionary : Dictionary> + { + } +} diff --git a/src/WebExpress/WebResource/ResourceFile.cs b/src/WebExpress/WebResource/ResourceFile.cs new file mode 100644 index 0000000..9c25353 --- /dev/null +++ b/src/WebExpress/WebResource/ResourceFile.cs @@ -0,0 +1,126 @@ +using WebExpress.WebMessage; +using static WebExpress.Internationalization.InternationalizationManager; + +namespace WebExpress.WebResource +{ + /// + /// Arbeitet eine Anfrage ab. Dies erfolgt nebenläufig + /// + public class ResourceFile : ResourceBinary + { + /// + /// Schutz bei Nebenläufgkeit + /// + private object Gard { get; set; } + + /// + /// Liefert oder setzt das Stammverzeichnis + /// + public string RootDirectory { get; protected set; } + + /// + /// Constructor + /// + public ResourceFile() + { + Gard = new object(); + } + + /// + /// Initialization + /// + /// The context. + public override void Initialization(IResourceContext context) + { + base.Initialization(context); + } + + /// + /// Processing of the resource. + /// + /// The request. + /// The response. + public override Response Process(Request request) + { + lock (Gard) + { + var contextPath = ResourceContext.ContextPath; + var url = request.Uri.ToString()[contextPath.ToString().Length..]; + + var path = System.IO.Path.GetFullPath(RootDirectory + url); + + if (!System.IO.File.Exists(path)) + { + return new ResponseNotFound(); + } + + Data = System.IO.File.ReadAllBytes(path); + + var response = base.Process(request); + response.Header.CacheControl = "public, max-age=31536000"; + + var extension = System.IO.Path.GetExtension(path); + extension = !string.IsNullOrWhiteSpace(extension) ? extension.ToLower() : ""; + + 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 ".xml": + response.Header.ContentType = "text/xml"; + break; + case ".html": + case ".htm": + response.Header.ContentType = "text/html"; + break; + case ".exe": + response.Header.ContentDisposition = "attatchment; filename=" + System.IO.Path.GetFileName(path) + "; size=" + Data.LongLength; + response.Header.ContentType = "application/octet-stream"; + break; + case ".zip": + response.Header.ContentDisposition = "attatchment; filename=" + System.IO.Path.GetFileName(path) + "; 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; + } + + request.ServerContext.Log.Debug(I18N("webexpress:resource.file", request.RemoteEndPoint, request.Uri)); + + return response; + } + } + } +} diff --git a/src/WebExpress/WebResource/ResourceItem.cs b/src/WebExpress/WebResource/ResourceItem.cs new file mode 100644 index 0000000..231febe --- /dev/null +++ b/src/WebExpress/WebResource/ResourceItem.cs @@ -0,0 +1,226 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; +using WebExpress.Internationalization; +using WebExpress.WebCondition; +using WebExpress.WebModule; +using WebExpress.WebUri; + +namespace WebExpress.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/WebResource/ResourceManager.cs b/src/WebExpress/WebResource/ResourceManager.cs new file mode 100644 index 0000000..15c8e2c --- /dev/null +++ b/src/WebExpress/WebResource/ResourceManager.cs @@ -0,0 +1,396 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using WebExpress.Internationalization; +using WebExpress.WebAttribute; +using WebExpress.WebComponent; +using WebExpress.WebCondition; +using WebExpress.WebModule; +using WebExpress.WebPlugin; +using WebExpress.WebScope; +using WebExpress.WebStatusPage; +using WebExpress.WebUri; + +namespace WebExpress.WebResource +{ + /// + /// The resource manager manages WebExpress elements, which can be called with a URI (Uniform Resource Identifier). + /// + public sealed class ResourceManager : IComponentPlugin, ISystemComponent + { + /// + /// An event that fires when an resource is added. + /// + public event EventHandler AddResource; + + /// + /// An event that fires when an resource is removed. + /// + 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 + .SelectMany(x => x.Values) + .SelectMany(x => x.ResourceContexts); + + /// + /// Constructor + /// + internal ResourceManager() + { + ComponentManager.PluginManager.AddPlugin += (sender, pluginContext) => + { + Register(pluginContext); + }; + + ComponentManager.PluginManager.RemovePlugin += (sender, pluginContext) => + { + Remove(pluginContext); + }; + + ComponentManager.ModuleManager.AddModule += (sender, moduleContext) => + { + AssignToModule(moduleContext); + }; + + ComponentManager.ModuleManager.RemoveModule += (sender, moduleContext) => + { + DetachFromModule(moduleContext); + }; + } + + /// + /// Initialization + /// + /// The reference to the context of the host. + public void Initialization(IHttpServerContext context) + { + HttpServerContext = context; + + HttpServerContext.Log.Debug + ( + InternationalizationManager.I18N("webexpress:resourcemanager.initialization") + ); + } + + /// + /// Discovers and registers resources from the specified plugin. + /// + /// A context of a plugin whose resources are to be registered. + public void Register(IPluginContext pluginContext) + { + if (Dictionary.ContainsKey(pluginContext)) + { + return; + } + + 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.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; + + foreach (var customAttribute in resourceType.CustomAttributes + .Where(x => x.AttributeType.GetInterfaces().Contains(typeof(IResourceAttribute)))) + { + 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(); + conditions.Add(Activator.CreateInstance(condition) as ICondition); + } + else if (customAttribute.AttributeType == typeof(CacheAttribute)) + { + cache = true; + } + else if (customAttribute.AttributeType == typeof(OptionalAttribute)) + { + optional = true; + } + } + + if (resourceType.GetInterfaces().Where(x => x == typeof(IScope)).Any()) + { + scopes.Add(resourceType.FullName?.ToLower()); + } + + if (string.IsNullOrEmpty(moduleId)) + { + // no module specified + HttpServerContext.Log.Warning + ( + InternationalizationManager.I18N + ( + "webexpress:resourcemanager.moduleless", + id + ) + ); + + continue; + } + + if (!dict.ContainsKey(id)) + { + var resourceItem = new ResourceItem() + { + ResourceId = id, + Title = title, + ParentId = parent, + ResourceClass = resourceType, + ModuleId = moduleId, + Scopes = scopes, + Cache = cache, + Conditions = conditions, + ContextPath = new UriResource(contextPath), + IncludeSubPaths = includeSubPaths, + PathSegment = segment.ToPathSegment(), + Optional = optional, + Log = HttpServerContext.Log + }; + + resourceItem.AddResource += (s, e) => + { + OnAddResource(e); + }; + + resourceItem.RemoveResource += (s, e) => + { + OnRemoveResource(e); + }; + + dict.Add(id, resourceItem); + + HttpServerContext.Log.Debug + ( + InternationalizationManager.I18N + ( + "webexpress:resourcemanager.addresource", + id, + moduleId + ) + ); + } + + // assign the resource to existing modules. + foreach (var moduleContext in ComponentManager.ModuleManager.GetModules(pluginContext, moduleId)) + { + AssignToModule(moduleContext); + } + } + } + + /// + /// Discovers and registers resources from the specified plugin. + /// + /// A list with plugin contexts that contain the resources. + public void Register(IEnumerable pluginContexts) + { + foreach (var pluginContext in pluginContexts) + { + Register(pluginContext); + } + } + + /// + /// Assign existing resources to the module. + /// + /// The context of the module. + private void AssignToModule(IModuleContext moduleContext) + { + foreach (var resourceItem in Dictionary.Values + .SelectMany(x => x.Values) + .Where(x => x.ModuleId.Equals(moduleContext?.ModuleId, StringComparison.OrdinalIgnoreCase)) + .Where(x => !x.IsAssociatedWithModule(moduleContext))) + { + resourceItem.AddModule(moduleContext); + } + } + + /// + /// Remove an existing modules to the application. + /// + /// The context of the module. + private void DetachFromModule(IModuleContext moduleContext) + { + foreach (var resourceItem in Dictionary.Values + .SelectMany(x => x.Values) + .Where(x => !x.IsAssociatedWithModule(moduleContext))) + { + resourceItem.DetachModule(moduleContext); + } + } + + /// + /// Renturns an enumeration of all containing resource items of a plugin. + /// + /// A context of a plugin whose resources are to be registered. + /// An enumeration of resource items. + internal IEnumerable GetResorceItems(IPluginContext pluginContext) + { + if (!Dictionary.ContainsKey(pluginContext)) + { + return new List(); + } + + return Dictionary[pluginContext].Values; + } + + /// + /// Renturns 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. + public IEnumerable GetResorces(IPluginContext pluginContext) + { + if (!Dictionary.ContainsKey(pluginContext)) + { + return new List(); + } + + return Dictionary[pluginContext].Values + .SelectMany(x => x.ResourceContexts); + } + + /// + /// Renturns 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) + { + return Dictionary.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)) + .FirstOrDefault(); + } + + /// + /// Removes all resources associated with the specified plugin context. + /// + /// The context of the plugin that contains the resources to remove. + public void Remove(IPluginContext pluginContext) + { + // the plugin has not been registered in the manager + if (!Dictionary.ContainsKey(pluginContext)) + { + return; + } + + foreach (var resourceItem in Dictionary[pluginContext].Values) + { + resourceItem.Dispose(); + } + + Dictionary.Remove(pluginContext); + } + + /// + /// 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); + } + + /// + /// 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 resourcenItem in GetResorceItems(pluginContext)) + { + output.Add + ( + string.Empty.PadRight(deep) + + InternationalizationManager.I18N + ( + "webexpress:resourcemanager.resource", + resourcenItem.ResourceId, + string.Join(",", resourcenItem.ModuleId) + ) + ); + } + } + } +} diff --git a/src/WebExpress/WebResource/ResourceRest.cs b/src/WebExpress/WebResource/ResourceRest.cs new file mode 100644 index 0000000..e924c88 --- /dev/null +++ b/src/WebExpress/WebResource/ResourceRest.cs @@ -0,0 +1,84 @@ +using System.Linq; +using System.Text.Json; +using WebExpress.WebMessage; + +namespace WebExpress.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/WebScope/IScope.cs b/src/WebExpress/WebScope/IScope.cs new file mode 100644 index 0000000..e9eef42 --- /dev/null +++ b/src/WebExpress/WebScope/IScope.cs @@ -0,0 +1,9 @@ +namespace WebExpress.WebScope +{ + /// + /// Interface of a scope. + /// + public interface IScope + { + } +} diff --git a/src/WebExpress/WebSession/AuthorizationService.cs b/src/WebExpress/WebSession/AuthorizationService.cs new file mode 100644 index 0000000..bab6b19 --- /dev/null +++ b/src/WebExpress/WebSession/AuthorizationService.cs @@ -0,0 +1,12 @@ +namespace WebExpress.WebSession +{ + public abstract class AuthorizationService + { + /// + /// Prüft ob der authentifizierte Nutzer autorisiert ist + /// + /// Die aktuelle Session + /// true, wenn autorisiert false sonst + public abstract bool Authorization(Session session); + } +} diff --git a/src/WebExpress/WebSession/ISessionProperty.cs b/src/WebExpress/WebSession/ISessionProperty.cs new file mode 100644 index 0000000..b15017f --- /dev/null +++ b/src/WebExpress/WebSession/ISessionProperty.cs @@ -0,0 +1,9 @@ +namespace WebExpress.WebSession +{ + /// + /// Interface of a property that can be assigned to a session. + /// + public interface ISessionProperty + { + } +} diff --git a/src/WebExpress/WebSession/Session.cs b/src/WebExpress/WebSession/Session.cs new file mode 100644 index 0000000..47a27d8 --- /dev/null +++ b/src/WebExpress/WebSession/Session.cs @@ -0,0 +1,126 @@ +using System; +using System.Collections.Generic; + +namespace WebExpress.WebSession +{ + /// + /// Represents a session.Through a session, session data can be assigned to + /// a user. Session data is stored on the server side, turning the stateless + /// http protocol into a state-based one. + /// + public class Session + { + /// + /// Returns the session id. + /// + public Guid Id { get; private set; } + + /// + /// Returns the creation time. + /// + public DateTime Created { get; private set; } + + /// + /// Returns or sets the time of the last access. + /// + public DateTime Updated { get; set; } + + /// + /// Returns or sets properties for the session. + /// + public Dictionary Properties { get; private set; } + + /// + /// Constructor + /// + public Session() + : this(Guid.NewGuid()) + { + } + + /// + /// Constructor + /// + /// The session id. + public Session(Guid id) + { + Id = id; + Created = DateTime.Now; + Updated = DateTime.Now; + + Properties = new Dictionary(); + } + + /// + /// Returns a session property. + /// + /// The type of the property. + /// The property or null. + public T GetProperty() where T : class, ISessionProperty, new() + { + lock (Properties) + { + if (Properties.ContainsKey(typeof(T))) + { + return Properties[typeof(T)] as T; + } + } + + return default; + } + + /// + /// 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() + { + lock (Properties) + { + if (Properties.ContainsKey(typeof(T))) + { + return Properties[typeof(T)] as T; + } + + var property = new T(); + SetProperty(property); + + return property; + } + } + + /// + /// Sets a property. + /// + /// The property to set. + public void SetProperty(ISessionProperty property) + { + lock (Properties) + { + if (!Properties.ContainsKey(property.GetType())) + { + Properties.Add(property.GetType(), property); + } + + Properties[property.GetType()] = property; + } + } + + /// + /// Removes a property. + /// + /// The type of the property. + public void RemoveProperty() where T : class, ISessionProperty, new() + { + lock (Properties) + { + if (Properties.ContainsKey(typeof(T))) + { + Properties.Remove(typeof(T)); + } + } + } + + } +} diff --git a/src/WebExpress/WebSession/SessionDictionary.cs b/src/WebExpress/WebSession/SessionDictionary.cs new file mode 100644 index 0000000..c880457 --- /dev/null +++ b/src/WebExpress/WebSession/SessionDictionary.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; + +namespace WebExpress.WebSession +{ + /// + /// Internal directory for storing session data. + /// kex = the session id + /// value = the session + /// + internal class SessionDictionary : Dictionary + { + } +} diff --git a/src/WebExpress/WebSession/SessionManager.cs b/src/WebExpress/WebSession/SessionManager.cs new file mode 100644 index 0000000..ae20638 --- /dev/null +++ b/src/WebExpress/WebSession/SessionManager.cs @@ -0,0 +1,98 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using WebExpress.Internationalization; +using WebExpress.WebComponent; +using WebExpress.WebMessage; +using WebExpress.WebPlugin; + +namespace WebExpress.WebSession +{ + public class SessionManager : IComponent, 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() + { + } + + /// + /// Initialization + /// + /// The reference to the context of the host. + public void Initialization(IHttpServerContext context) + { + HttpServerContext = context; + + HttpServerContext.Log.Debug + ( + InternationalizationManager.I18N("webexpress:sessionmanager.initialization") + ); + } + + /// + /// Creates a session or returns an existing session. + /// + /// The request. + /// The session. + public Session GetSession(Request request) + { + var session = default(Session); + + // Session ermitteln + var sessionCookie = request?.Header + .Cookies?.Where(x => x.Name.Equals("session", StringComparison.OrdinalIgnoreCase)) + .FirstOrDefault(); + + Guid Guid = Guid.NewGuid(); + + try + { + Guid = Guid.Parse(sessionCookie?.Value); + } + catch + { + + } + + if (sessionCookie != null && Dictionary.ContainsKey(Guid)) + { + session = Dictionary[Guid]; + session.Updated = DateTime.Now; + } + else + { + // no or invalid session => assign new session + session = new Session(Guid); + + lock (Dictionary) + { + Dictionary[Guid] = session; + } + } + + return session; + } + + /// + /// 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) + { + } + } +} diff --git a/src/WebExpress/WebSession/SessionProperty.cs b/src/WebExpress/WebSession/SessionProperty.cs new file mode 100644 index 0000000..6feec8a --- /dev/null +++ b/src/WebExpress/WebSession/SessionProperty.cs @@ -0,0 +1,9 @@ +namespace WebExpress.WebSession +{ + /// + /// Base class of a property that can be assigned to a session. + /// + public abstract class SessionProperty : ISessionProperty + { + } +} diff --git a/src/WebExpress/WebSession/SessionPropertyAuthentification.cs b/src/WebExpress/WebSession/SessionPropertyAuthentification.cs new file mode 100644 index 0000000..d5d59fb --- /dev/null +++ b/src/WebExpress/WebSession/SessionPropertyAuthentification.cs @@ -0,0 +1,15 @@ +namespace WebExpress.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/WebSession/SessionPropertyAuthorization.cs b/src/WebExpress/WebSession/SessionPropertyAuthorization.cs new file mode 100644 index 0000000..911af46 --- /dev/null +++ b/src/WebExpress/WebSession/SessionPropertyAuthorization.cs @@ -0,0 +1,6 @@ +namespace WebExpress.WebSession +{ + public class SessionPropertyAuthorization : SessionProperty + { + } +} diff --git a/src/WebExpress/WebSession/SessionPropertyParameter.cs b/src/WebExpress/WebSession/SessionPropertyParameter.cs new file mode 100644 index 0000000..fe3b5a6 --- /dev/null +++ b/src/WebExpress/WebSession/SessionPropertyParameter.cs @@ -0,0 +1,30 @@ +using System.Collections.Generic; +using WebExpress.WebMessage; + +namespace WebExpress.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/WebSitemap/SearchContext.cs b/src/WebExpress/WebSitemap/SearchContext.cs new file mode 100644 index 0000000..51b1185 --- /dev/null +++ b/src/WebExpress/WebSitemap/SearchContext.cs @@ -0,0 +1,26 @@ +using System.Globalization; +using WebExpress.WebMessage; + +namespace WebExpress.WebSitemap +{ + /// + /// The search context for searches within the sitemap. + /// + public class SearchContext + { + /// + /// Returns the culture. + /// + public CultureInfo Culture { get; internal set; } + + /// + /// Returns the http context. + /// + public HttpContext HttpContext { get; internal set; } + + /// + /// Returns the server context. + /// + public IHttpServerContext HttpServerContext { get; internal set; } + } +} diff --git a/src/WebExpress/WebSitemap/SearchResult.cs b/src/WebExpress/WebSitemap/SearchResult.cs new file mode 100644 index 0000000..172e249 --- /dev/null +++ b/src/WebExpress/WebSitemap/SearchResult.cs @@ -0,0 +1,74 @@ +using System.Collections.Generic; +using WebExpress.WebApplication; +using WebExpress.WebModule; +using WebExpress.WebResource; +using WebExpress.WebUri; + +namespace WebExpress.WebSitemap +{ + /// + /// Represents the search result within the site map. + /// + public class SearchResult + { + /// + /// Returns the resource id. + /// + 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; } + + /// + /// 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. + /// + public IReadOnlyList ResourceContextFilter { get; internal set; } + + /// + /// Returns the path. + /// + /// The path. + public ICollection Path { get; internal set; } + + /// + /// Returns the uri. + /// + /// The uri. + public UriResource Uri { get; internal set; } + + /// + /// Constructor + /// + internal SearchResult() + { + + } + } +} diff --git a/src/WebExpress/WebSitemap/SitemapManager.cs b/src/WebExpress/WebSitemap/SitemapManager.cs new file mode 100644 index 0000000..89dd6ce --- /dev/null +++ b/src/WebExpress/WebSitemap/SitemapManager.cs @@ -0,0 +1,514 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using WebExpress.Internationalization; +using WebExpress.WebApplication; +using WebExpress.WebComponent; +using WebExpress.WebMessage; +using WebExpress.WebModule; +using WebExpress.WebPage; +using WebExpress.WebPlugin; +using WebExpress.WebResource; +using WebExpress.WebUri; + +namespace WebExpress.WebSitemap +{ + /// + /// The resource manager manages WebExpress elements, which can be called with a URI (Uniform Resource Identifier). + /// + public sealed class SitemapManager : IComponent, ISystemComponent + { + /// + /// Returns the reference to the context of the host. + /// + public IHttpServerContext HttpServerContext { get; private set; } + + /// + /// Returns the side map. + /// + private SitemapNode SiteMap { get; set; } = new SitemapNode(); + + /// + /// Constructor + /// + internal SitemapManager() + { + + } + + /// + /// Initialization + /// + /// The reference to the context of the host. + public void Initialization(IHttpServerContext context) + { + HttpServerContext = context; + + HttpServerContext.Log.Debug + ( + InternationalizationManager.I18N("webexpress:sitemapmanager.initialization") + ); + } + + /// + /// Rebuilds the sitemap. + /// + public void Refresh() + { + var newSiteMapNode = new SitemapNode() { PathSegment = new UriPathSegmentRoot() }; + + HttpServerContext.Log.Debug + ( + InternationalizationManager.I18N("webexpress:sitemapmanager.refresh") + ); + + // applications + var applications = ComponentManager.ApplicationManager.Applications + .Select(x => new + { + ApplicationContext = x, + x.ContextPath.PathSegments + }) + .OrderBy(x => x.PathSegments.Count()); + + foreach (var application in applications) + { + MergeSitemap(newSiteMapNode, CreateSiteMap + ( + new Queue(application.PathSegments), + application.ApplicationContext + )); + } + + // modules + var modules = ComponentManager.ModuleManager.Modules + .Select(x => new + { + ModuleContext = x, + x.ContextPath.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 + )); + } + + SiteMap = newSiteMapNode; + + using (var frame = new LogFrameSimple(HttpServerContext.Log)) + { + var list = new List(); + PrepareForLog(null, list, 2); + HttpServerContext.Log.Info(string.Join(Environment.NewLine, list)); + } + } + + /// + /// Locates the resource associated with the Uri. + /// + /// The Uri. + /// The search context. + /// The search result with the found resource or null + 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)))), + new Queue(), + searchContext + ); + + if (result != null && result.ResourceContext != null) + { + if (!result.ResourceContext.Conditions.Any() || result.ResourceContext.Conditions.All(x => x.Fulfillment(searchContext.HttpContext?.Request))) + { + return result; + } + } + + // 404 + return result; + } + + /// + /// Determines the Uri from the sitemap of a class, taking into account the 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 + { + var node = SiteMap.GetPreOrder() + .Where(x => x.ResourceItem?.ResourceClass == typeof(T)) + .FirstOrDefault(); + + return node?.ResourceContext?.Uri.SetParameters(parameters); + } + + /// + /// Determines the Uri from the sitemap of a class, taking into account the 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 + { + var node = SiteMap.GetPreOrder() + .Where(x => x.ResourceItem?.ResourceClass == typeof(T)) + .Where(x => x.ModuleContext == moduleContext) + .FirstOrDefault(); + + return node?.ResourceContext?.Uri; + } + + /// + /// Determines the Uri from the sitemap of a class, taking into account the 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(IResourceContext resourceContext) where T : IResource + { + var node = SiteMap.GetPreOrder() + .Where(x => x.ResourceItem?.ResourceClass == typeof(T)) + .Where(x => x.ModuleContext == resourceContext.ModuleContext) + .FirstOrDefault(); + + 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 parent node or null if root. + /// The sitemap root node. + private static SitemapNode CreateSiteMap + ( + Queue contextPathSegments, + IApplicationContext applicationContext, + SitemapNode parent = null + ) + { + var pathSegment = contextPathSegments.Any() ? contextPathSegments.Dequeue() : null; + var node = new SitemapNode() + { + PathSegment = pathSegment as IUriPathSegment, + Parent = parent, + ApplicationContext = applicationContext + }; + + if (contextPathSegments.Any()) + { + node.Children.Add(CreateSiteMap(contextPathSegments, applicationContext, node)); + } + + return node; + } + + /// + /// Creates the sitemap. Works recursively. + /// It is important for the algorithm that the addition of module 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 sitemap root node. + private static SitemapNode CreateSiteMap + ( + Queue contextPathSegments, + IModuleContext moduleContext, + SitemapNode parent = null + ) + { + var pathSegment = contextPathSegments.Any() ? contextPathSegments.Dequeue() : null; + var node = new SitemapNode() + { + PathSegment = pathSegment as IUriPathSegment, + Parent = parent, + ApplicationContext = moduleContext?.ApplicationContext, + ModuleContext = moduleContext + }; + + if (contextPathSegments.Any()) + { + node.Children.Add(CreateSiteMap(contextPathSegments, moduleContext, node)); + } + + return node; + } + + /// + /// Creates the sitemap. Works recursively. + /// It is important for the algorithm that the addition of resources 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 parent node or null if root. + /// The sitemap parent node. + private static SitemapNode CreateSiteMap + ( + Queue contextPathSegments, + ResourceItem resourceItem, + IResourceContext resourceContext, + SitemapNode parent = null + ) + { + var pathSegment = contextPathSegments.Any() ? contextPathSegments.Dequeue() : null; + var node = new SitemapNode() + { + PathSegment = pathSegment as IUriPathSegment, + Parent = parent, + ResourceItem = !contextPathSegments.Any() ? resourceItem : null, + ApplicationContext = resourceContext?.ModuleContext?.ApplicationContext, + ModuleContext = resourceContext?.ModuleContext, + ResourceContext = resourceContext + }; + + if (contextPathSegments.Any()) + { + node.Children.Add(CreateSiteMap(contextPathSegments, resourceItem, resourceContext, node)); + } + + return node; + } + + /// + /// Merges one sitemap with another. Works recursively. + /// + /// The first sitemap to be merged. + /// The second sitemap to be merged. + private void MergeSitemap(SitemapNode first, SitemapNode second) + { + if (first.PathSegment.Equals(second.PathSegment)) + { + foreach (var sc in second.Children) + { + 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; + } + + MergeSitemap(fc, sc); + return; + } + + first.Children.Add(sc); + } + } + + return; + } + + /// + /// Locates the resource associated with the Uri. Works recursively. + /// + /// The sitemap node. + /// The path segments. + /// The path segments. + /// The search context. + /// The search result with the found resource + private SearchResult SearchNode + ( + SitemapNode node, + Queue inPathSegments, + Queue outPathSegments, + SearchContext searchContext + ) + { + var pathSegment = inPathSegments.Any() ? inPathSegments.Dequeue() : null; + var nextPathSegment = inPathSegments.Any() ? inPathSegments.Peek() : null; + + if (IsMatched(node, pathSegment)) + { + var copy = node.PathSegment.Copy(); + if (copy is UriPathSegmentVariable variable) + { + variable.Value = pathSegment; + } + + outPathSegments.Enqueue(copy); + + if (nextPathSegment == null && node.ResourceItem != null) + { + return new SearchResult() + { + Id = node.ResourceItem.ResourceId, + Title = node.ResourceItem.Title, + ApplicationContext = node.ApplicationContext, + ModuleContext = node.ModuleContext, + ResourceContext = node.ResourceContext, + SearchContext = searchContext, + Uri = new UriResource(outPathSegments.ToArray()), + Instance = CreateInstance(node, new UriResource(outPathSegments.ToArray()), searchContext), + }; + } + else if (node.IsLeaf && nextPathSegment != null && node.ResourceItem.IncludeSubPaths && node.ResourceItem != null) + { + return new SearchResult() + { + Id = node.ResourceItem.ResourceId, + Title = node.ResourceItem.Title, + ApplicationContext = node.ApplicationContext, + ModuleContext = node.ModuleContext, + ResourceContext = node.ResourceContext, + SearchContext = searchContext, + Uri = new UriResource(outPathSegments.ToArray()), + Instance = CreateInstance(node, new UriResource(outPathSegments.ToArray()), searchContext), + }; + } + + foreach (var child in node.Children.Where(x => IsMatched(x, nextPathSegment))) + { + return SearchNode(child, inPathSegments, outPathSegments, 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; + } + + /// + /// Checks whether the node matches the path element. + /// + /// The sitemap node. + /// The path segments. + /// True if the path element matched, false otherwise. + private static bool IsMatched(SitemapNode node, string pathSegement) + { + if (node == null || string.IsNullOrWhiteSpace(pathSegement)) + { + return false; + } + + return node.PathSegment.IsMatched(pathSegement); + } + + /// + /// 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) + { + output.Add + ( + InternationalizationManager.I18N + ( + "webexpress:sitemapmanager.sitemap" + ) + ); + + var preorder = SiteMap + .GetPreOrder() + .Select(x => InternationalizationManager.I18N + ( + "webexpress:sitemapmanager.preorder", + " " + x.ToString().PadRight(60), + x.ResourceItem?.ResourceId ?? "" + )); + + foreach (var node in preorder) + { + output.Add(node); + } + } + } +} diff --git a/src/WebExpress/WebSitemap/SitemapNode.cs b/src/WebExpress/WebSitemap/SitemapNode.cs new file mode 100644 index 0000000..3fce05a --- /dev/null +++ b/src/WebExpress/WebSitemap/SitemapNode.cs @@ -0,0 +1,178 @@ +using System.Collections.Generic; +using System.Linq; +using WebExpress.WebApplication; +using WebExpress.WebModule; +using WebExpress.WebResource; +using WebExpress.WebUri; + +namespace WebExpress.WebSitemap +{ + /// + /// A Sitemap node. + /// + public class SitemapNode + { + /// + /// Returns the node path segment. + /// + public IUriPathSegment PathSegment { get; internal set; } + + /// + /// Returns the resource item. + /// + 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; } + + /// + /// Returns the child nodes. + /// + public ICollection Children { get; private set; } = new List(); + + /// + /// Returns the parent node. + /// + public SitemapNode Parent { get; internal set; } + + /// + /// returns the root. + /// + public SitemapNode Root + { + get + { + if (IsRoot) + { + return this; + } + + var parent = Parent; + while (!parent.IsRoot) + { + parent = parent.Parent; + } + + return parent; + } + } + + /// + /// Checks whether the node is the root. + /// + /// true if root, otherwise false. + public bool IsRoot => Parent == null; + + /// + /// Checks whether the node is a leaf. + /// + /// true if a leaf, otherwise false. + public bool IsLeaf => !Children.Any(); + + /// + /// Returns the path. + /// + /// The path. + public ICollection Path + { + get + { + var list = new List + { + this + }; + + var parent = Parent; + while (parent != null) + { + list.Add(parent); + + parent = parent.Parent; + } + + list.Reverse(); + + return list; + } + } + + /// + /// Constructor + /// + public SitemapNode() + { + + } + + /// + /// Passes through the tree in pre order. + /// + /// The tree as a list. + public IEnumerable GetPreOrder() + { + var list = new List + { + this + }; + + foreach (var child in Children.OrderBy(x => x.PathSegment?.Id)) + { + list.AddRange(child.GetPreOrder()); + } + + return list; + } + + /// + /// Creates a copy of the sitemap node. + /// + /// The copy of the sitemap node. + public SitemapNode Copy() + { + var node = new SitemapNode() + { + PathSegment = PathSegment, + ResourceItem = ResourceItem, + ApplicationContext = ApplicationContext, + ModuleContext = ModuleContext, + ResourceContext = ResourceContext, + Instance = Instance, + Parent = Parent, + Children = Children.Select(x => x.Copy()).ToList() + }; + + return node; + } + + /// + /// Convert to string. + /// + /// The tree node in its string representation. + public override string ToString() + { + return Path.FirstOrDefault()?.PathSegment + string.Join + ( + "/", + Path.Where(x => !(x.PathSegment is UriPathSegmentRoot)) + .Select(x => x.PathSegment?.ToString()) + ); + } + } +} diff --git a/src/WebExpress/WebStatusPage/IStatusPage.cs b/src/WebExpress/WebStatusPage/IStatusPage.cs new file mode 100644 index 0000000..f2d1e50 --- /dev/null +++ b/src/WebExpress/WebStatusPage/IStatusPage.cs @@ -0,0 +1,62 @@ +using WebExpress.WebApplication; +using WebExpress.WebMessage; +using WebExpress.WebModule; +using WebExpress.WebResource; +using WebExpress.WebUri; + +namespace WebExpress.WebStatusPage +{ + /// + /// Statusseite + /// + public interface 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; } + + /// + /// Liefert oder setzt den Statuscode + /// + int StatusCode { get; set; } + + /// + /// Liefert oder setzt die Stausnachricht + /// + string StatusTitle { get; set; } + + /// + /// Liefert oder setzt die Stausnachricht + /// + string StatusMessage { get; set; } + + /// + /// Liefert oder setzt das Statusicon + /// + UriResource StatusIcon { get; set; } + + /// + /// Initialization + /// + /// The context of the resource. + void Initialization(IResourceContext resourceContext); + + /// + /// Processing of the resource. + /// + /// The request. + /// The response. + Response Process(Request request); + } +} diff --git a/src/WebExpress/WebStatusPage/ResponseDictionary.cs b/src/WebExpress/WebStatusPage/ResponseDictionary.cs new file mode 100644 index 0000000..f63cfb3 --- /dev/null +++ b/src/WebExpress/WebStatusPage/ResponseDictionary.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; +using WebExpress.WebPlugin; + +namespace WebExpress.WebStatusPage +{ + /// + /// key = plugin context + /// value = ResponseDictionaryItem + /// + public class ResponseDictionary : Dictionary + { + } +} diff --git a/src/WebExpress/WebStatusPage/ResponseDictionaryItem.cs b/src/WebExpress/WebStatusPage/ResponseDictionaryItem.cs new file mode 100644 index 0000000..6c839b0 --- /dev/null +++ b/src/WebExpress/WebStatusPage/ResponseDictionaryItem.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; + +namespace WebExpress.WebStatusPage +{ + /// + /// key = statuscode + /// value = status page item + /// + public class ResponseDictionaryItem : Dictionary + { + } +} diff --git a/src/WebExpress/WebStatusPage/ResponseItem.cs b/src/WebExpress/WebStatusPage/ResponseItem.cs new file mode 100644 index 0000000..1daf9aa --- /dev/null +++ b/src/WebExpress/WebStatusPage/ResponseItem.cs @@ -0,0 +1,33 @@ +using System; +using WebExpress.WebPlugin; + +namespace WebExpress.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/WebStatusPage/ResponseManager.cs b/src/WebExpress/WebStatusPage/ResponseManager.cs new file mode 100644 index 0000000..4dd7021 --- /dev/null +++ b/src/WebExpress/WebStatusPage/ResponseManager.cs @@ -0,0 +1,346 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using WebExpress.Internationalization; +using WebExpress.WebAttribute; +using WebExpress.WebComponent; +using WebExpress.WebPlugin; +using WebExpress.WebResource; + +namespace WebExpress.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/WebTask/ITask.cs b/src/WebExpress/WebTask/ITask.cs new file mode 100644 index 0000000..4998162 --- /dev/null +++ b/src/WebExpress/WebTask/ITask.cs @@ -0,0 +1,52 @@ +using System; + +namespace WebExpress.WebTask +{ + public interface ITask + { + /// + /// Event is triggered when the task is executed. + /// + event EventHandler Process; + + /// + /// Event is triggered when the task ends. + /// + event EventHandler Finish; + + /// + /// The id of the task. + /// + string Id { get; } + + /// + /// Returns the state in which the task is located. + /// + TaskState State { get; } + + /// + /// Returns the progress of the task. The value range is from 0 to 100. + /// + int Progress { get; set; } + + /// + /// Returns or sets a message that provides information about the processing. + /// + string Message { get; set; } + + /// + /// Initialization + /// + void Initialization(); + + /// + /// Starts the execution concurrently. + /// + void Run(); + + /// + /// Abandonment of an existing processing. + /// + void Cancel(); + } +} diff --git a/src/WebExpress/WebTask/Task.cs b/src/WebExpress/WebTask/Task.cs new file mode 100644 index 0000000..a244656 --- /dev/null +++ b/src/WebExpress/WebTask/Task.cs @@ -0,0 +1,118 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using WebExpress.WebComponent; + +namespace WebExpress.WebTask +{ + public class Task : ITask + { + /// + /// Internal management of progress. + /// + private int _Progress { get; set; } + + /// + /// Event is triggered when the task is executed. + /// + public event EventHandler Process; + + /// + /// Event is triggered when the task is terminated. + /// + public event EventHandler Finish; + + /// + /// The id of the task. + /// + public string Id { get; internal set; } + + /// + /// Returns the state in which the task is located. + /// + public TaskState State { get; internal set; } + + /// + /// The arguments. + /// + public ICollection Arguments { get; internal set; } + + /// + /// Thread termination of the task. + /// + private CancellationTokenSource TokenSource { get; } = new CancellationTokenSource(); + + /// + /// Retiurns the progress of the task. The value range is from 0 to 100. + /// + public int Progress + { + get => _Progress; + set => _Progress = Math.Min(value, 100); + } + + /// + /// Returns or sets a message that provides information about the processing. + /// + public string Message { get; set; } + + /// + /// Initialization + /// + public virtual void Initialization() + { + } + + /// + /// Processing of the resource. + /// + protected virtual void OnProcess() + { + Process?.Invoke(this, new TaskEventArgs()); + } + + /// + /// Triggered when the task is complete. + /// + protected virtual void OnFinish() + { + Finish?.Invoke(this, new TaskEventArgs()); + } + + /// + /// Starts the execution concurrently. + /// + public void Run() + { + System.Threading.Tasks.Task.Factory.StartNew((Action)(() => + { + State = TaskState.Run; + + this.Progress = 0; + + OnProcess(); + + this.Progress = 100; + + State = TaskState.Finish; + + OnFinish(); + + ComponentManager.TaskManager.RemoveTask(this); + + }), TokenSource.Token); + } + + /// + /// Abandonment of an existing processing. + /// + public void Cancel() + { + TokenSource.Cancel(); + + State = TaskState.Canceled; + + ComponentManager.TaskManager.RemoveTask(this); + } + } +} diff --git a/src/WebExpress/WebTask/TaskDictionary.cs b/src/WebExpress/WebTask/TaskDictionary.cs new file mode 100644 index 0000000..3ba0714 --- /dev/null +++ b/src/WebExpress/WebTask/TaskDictionary.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; + +namespace WebExpress.WebTask +{ + /// + /// Verzeichnis mit dem aktuellen Aufgaben + /// Key = Die Task-Id + /// + internal class TaskDictionary : Dictionary + { + } +} diff --git a/src/WebExpress/WebTask/TaskEventArgs.cs b/src/WebExpress/WebTask/TaskEventArgs.cs new file mode 100644 index 0000000..5da52c9 --- /dev/null +++ b/src/WebExpress/WebTask/TaskEventArgs.cs @@ -0,0 +1,8 @@ +using System; + +namespace WebExpress.WebTask +{ + public class TaskEventArgs : EventArgs + { + } +} diff --git a/src/WebExpress/WebTask/TaskManager.cs b/src/WebExpress/WebTask/TaskManager.cs new file mode 100644 index 0000000..ab79a39 --- /dev/null +++ b/src/WebExpress/WebTask/TaskManager.cs @@ -0,0 +1,155 @@ +using System; +using System.Collections.Generic; +using WebExpress.Internationalization; +using WebExpress.WebComponent; +using WebExpress.WebPlugin; + +namespace WebExpress.WebTask +{ + /// + /// Management of ad-hoc tasks. + /// + public class TaskManager : IComponent, 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(); + + /// + /// Constructor + /// + internal TaskManager() + { + } + + /// + /// Initialization + /// + /// The reference to the context of the host. + public void Initialization(IHttpServerContext context) + { + HttpServerContext = context; + + HttpServerContext.Log.Debug + ( + InternationalizationManager.I18N("webexpress:applicationmanager.initialization") + ); + } + + /// + /// Checks if a task has already been created. + /// + /// The id of the task. + /// True if this task already exists, false otherwise. + public bool ContainsTask(string id) + { + return Dictionary.ContainsKey(id?.ToLower()); + } + + /// + /// Returns an existing task. + /// + /// The id of the task. + /// The task or null. + public ITask GetTask(string id) + { + if (Dictionary.ContainsKey(id?.ToLower())) + { + return Dictionary[id?.ToLower()]; + } + + return null; + } + + /// + /// Creates a new task or returns an existing task. + /// + /// The id of the task. + /// The event argument. + /// The task or null. + public ITask CreateTask(string id, params object[] args) + { + var key = id?.ToLower(); + + if (!Dictionary.ContainsKey(id)) + { + var task = new Task() { Id = id, State = TaskState.Created, Arguments = args }; + Dictionary.Add(key, task); + + task.Initialization(); + + return task; + } + + return Dictionary[id]; + } + + /// + /// Creates a new task or returns an existing task. + /// + /// The id of the task. + /// The event handler. + /// The event argument. + /// The task or null. + public ITask CreateTask(string id, EventHandler handler, params object[] args) + { + return CreateTask(id, handler, 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. + public ITask CreateTask(string id, EventHandler handler, params object[] args) where T : Task, new() + { + var key = id?.ToLower(); + + if (!Dictionary.ContainsKey(id)) + { + var task = new Task() { Id = id, State = TaskState.Created, Arguments = args }; + Dictionary.Add(key, task); + + task.Initialization(); + + task.Process += handler; + + return task; + } + + return Dictionary[id]; + } + + /// + /// Removes a task. + /// + /// The task. + public void RemoveTask(ITask task) + { + var key = task?.Id.ToLower(); + + if (Dictionary.ContainsKey(key)) + { + Dictionary.Remove(key); + } + } + + /// + /// 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) + { + } + } +} diff --git a/src/WebExpress/WebTask/TaskState.cs b/src/WebExpress/WebTask/TaskState.cs new file mode 100644 index 0000000..7ee968f --- /dev/null +++ b/src/WebExpress/WebTask/TaskState.cs @@ -0,0 +1,22 @@ +namespace WebExpress.WebTask +{ + public enum TaskState + { + /// + /// The task has been created and is waiting to be executed. + /// + Created, + /// + /// The task is in the process of being executed. + /// + Run, + /// + /// The task was aborted. + /// + Canceled, + /// + /// The task has ended. + /// + Finish + } +} diff --git a/src/WebExpress/WebUri/IUriPathSegment.cs b/src/WebExpress/WebUri/IUriPathSegment.cs new file mode 100644 index 0000000..ba9f230 --- /dev/null +++ b/src/WebExpress/WebUri/IUriPathSegment.cs @@ -0,0 +1,61 @@ +using System.Globalization; + +namespace WebExpress.WebUri +{ + /// + /// The path segment of a resource uri. + /// + public interface IUriPathSegment + { + /// + /// Returns or sets the id. + /// + internal string Id { get; } + + /// + /// Returns the value. + /// + string Value { get; } + + /// + /// Returns or sets the display text. + /// + string Display { get; set; } + + /// + /// Returns the tag. + /// + object Tag { get; } + + /// + /// Checks for empty path segment. + /// + bool IsEmpty { get; } + + /// + /// Checks whether the node matches the path element. + /// + /// The value to check. + /// True if the path element matched, false otherwise. + bool IsMatched(string value); + + /// + /// Make a deep copy. + /// + /// The copy. + IUriPathSegment Copy(); + + /// + /// Compare the object. + /// + /// The comparison object. + /// true if equals, false otherwise + bool Equals(IUriPathSegment obj); + + /// + /// Returns or sets the display text. + /// + /// The culture. + string GetDisplay(CultureInfo culture); + } +} \ No newline at end of file diff --git a/src/WebExpress/WebUri/IUriPathSegmentConstant.cs b/src/WebExpress/WebUri/IUriPathSegmentConstant.cs new file mode 100644 index 0000000..12fd647 --- /dev/null +++ b/src/WebExpress/WebUri/IUriPathSegmentConstant.cs @@ -0,0 +1,10 @@ +namespace WebExpress.WebUri +{ + /// + /// The path segment of a uri. + /// + public interface IUriPathSegmentConstant : IUriPathSegment + { + + } +} \ No newline at end of file diff --git a/src/WebExpress/WebUri/IUriPathSegmentVariable.cs b/src/WebExpress/WebUri/IUriPathSegmentVariable.cs new file mode 100644 index 0000000..72cd80e --- /dev/null +++ b/src/WebExpress/WebUri/IUriPathSegmentVariable.cs @@ -0,0 +1,32 @@ +using System.Collections.Generic; + +namespace WebExpress.WebUri +{ + /// + /// The path segment of a uri. + /// + public interface IUriPathSegmentVariable : IUriPathSegment + { + /// + /// Returns or sets the value. + /// + new string Value { get; set; } + + /// + /// Returns the variable name. + /// + string VariableName { get; } + + /// + /// Returns the regex expression. + /// + string Expression { get; } + + /// + /// Returns the variable. + /// + /// The value. + /// The variable value pair. + IDictionary GetVariable(string value); + } +} \ No newline at end of file diff --git a/src/WebExpress/WebUri/UriAuthority.cs b/src/WebExpress/WebUri/UriAuthority.cs new file mode 100644 index 0000000..3cecb9e --- /dev/null +++ b/src/WebExpress/WebUri/UriAuthority.cs @@ -0,0 +1,90 @@ +using System; +using System.Linq; + +namespace WebExpress.WebUri +{ + /// + /// The authority (e.g. user@example.com:8080). + /// + public class UriAuthority + { + /// + /// User information. + /// + public string User { get; set; } + + /// + /// The password. + /// is deprecated in RFC 3986 (section 3.2.1). + /// + [Obsolete("The property is deprecated (see RFC 3986 Section 3.2.1).", false)] + public string Password { get; set; } + + /// + /// The host (e.g. example.com, 192.0.2.16:80). + /// + public string Host { get; set; } + + /// + /// The port (e.g. example.com, 192.0.2.16:80). + /// + public int? Port { get; set; } + + /// + /// Constructor + /// + public UriAuthority() + { + + } + + /// + /// Constructor + /// + /// The host. + public UriAuthority(string host) + { + Host = host; + } + + /// + /// Constructor + /// + /// The host. + /// The port. + public UriAuthority(string host, int port) + { + Host = host; + Port = port; + } + + /// + /// Converts the authority to a string. + /// + /// The string representation of the authority. + public override string ToString() + { + return ToString(-1); + } + + /// + /// Converts the authority to a string. + /// + /// The default port for the active scheme. + /// The string representation of the authority. + public virtual string ToString(int defaultPort) + { +#pragma warning disable 618 + var userinfo = string.Join(":", new string[] { User, Password }.Where(x => !string.IsNullOrWhiteSpace(x))); +#pragma warning restore 618 + + var adress = string.Join(":", new string[] + { + Host, + Port != defaultPort ? Port?.ToString() : "" + }.Where(x => !string.IsNullOrWhiteSpace(x))); + + return "//" + string.Join("@", new string[] { userinfo, adress }.Where(x => !string.IsNullOrWhiteSpace(x))); + } + } +} \ No newline at end of file diff --git a/src/WebExpress/WebUri/UriFragment.cs b/src/WebExpress/WebUri/UriFragment.cs new file mode 100644 index 0000000..7017948 --- /dev/null +++ b/src/WebExpress/WebUri/UriFragment.cs @@ -0,0 +1,26 @@ +namespace WebExpress.WebUri +{ + /// + /// Uri which consists only of the fragment (e.g. #). + /// + public class UriFragment : UriResource + { + /// + /// Constructor + /// + public UriFragment() + { + } + + /// + /// Converts the uri to a string. + /// + /// The string representation of the uri. + public override string ToString() + { + return "#" + Fragment; + } + + + } +} \ No newline at end of file diff --git a/src/WebExpress/WebUri/UriPathSegmentConstant.cs b/src/WebExpress/WebUri/UriPathSegmentConstant.cs new file mode 100644 index 0000000..56828aa --- /dev/null +++ b/src/WebExpress/WebUri/UriPathSegmentConstant.cs @@ -0,0 +1,122 @@ +using System; +using System.Globalization; +using WebExpress.Internationalization; + +namespace WebExpress.WebUri +{ + /// + /// constant path segment. + /// + public class UriPathSegmentConstant : IUriPathSegmentConstant + { + /// + /// Returns or sets the id. + /// + public string Id => Value?.ToLower(); + + /// + /// Returns or sets the path text. + /// + public string Value { get; set; } + + /// + /// Returns or sets the display text. + /// + public string Display { get; set; } + + /// + /// Returns or sets the tag. + /// + public object Tag { get; set; } + + /// + /// Checks for empty path segment. + /// + public bool IsEmpty => string.IsNullOrWhiteSpace(Value) || Value.Equals("/"); + + /// + /// Constructor + /// + /// The name. + /// The tag or null + public UriPathSegmentConstant(string value, object tag = null) + : this(value, null, tag) + { + } + + /// + /// Constructor + /// + /// The name. + /// The display text. + /// The tag or null + public UriPathSegmentConstant(string value, string display, object tag = null) + { + Value = value ?? string.Empty; + Display = display; + Tag = tag; + } + + /// + /// Checks whether the node matches the path element. + /// + /// The value to check. + /// True if the path element matched, false otherwise. + public bool IsMatched(string value) + { + if (string.IsNullOrWhiteSpace(value)) + { + return false; + } + + return Value.Equals(value, StringComparison.OrdinalIgnoreCase) || + (Value + "/").Equals(value, StringComparison.OrdinalIgnoreCase); + } + + /// + /// Make a deep copy. + /// + /// The copy. + public virtual IUriPathSegment Copy() + { + return new UriPathSegmentConstant(Value, Display, Tag); + } + + /// + /// Compare the object. + /// + /// The comparison object. + /// true if equals, false otherwise + public virtual bool Equals(IUriPathSegment obj) + { + if (obj == null) + { + return false; + } + else if (obj is UriPathSegmentConstant segment) + { + return Value.Equals(segment.Value, StringComparison.OrdinalIgnoreCase); + } + + return false; + } + + /// + /// Returns or sets the display text. + /// + /// The culture. + public virtual string GetDisplay(CultureInfo culture) + { + return InternationalizationManager.I18N(culture, Display); + } + + /// + /// Converts the segment to a string. + /// + /// A string that represents the current segment. + public override string ToString() + { + return Value ?? ""; + } + } +} \ No newline at end of file diff --git a/src/WebExpress/WebUri/UriPathSegmentRoot.cs b/src/WebExpress/WebUri/UriPathSegmentRoot.cs new file mode 100644 index 0000000..a655998 --- /dev/null +++ b/src/WebExpress/WebUri/UriPathSegmentRoot.cs @@ -0,0 +1,108 @@ +using System; +using System.Globalization; +using WebExpress.Internationalization; + +namespace WebExpress.WebUri +{ + /// + /// constant path segment. + /// + public class UriPathSegmentRoot : IUriPathSegment + { + /// + /// Returns or sets the id. + /// + public string Id => "ROOT"; + + /// + /// Returns or sets the path text. + /// + public string Value { get; set; } + + /// + /// Returns or sets the display text. + /// + public string Display { get; set; } + + /// + /// Returns or sets the tag. + /// + public object Tag { get; set; } + + /// + /// Checks for empty path segment. + /// + public bool IsEmpty => false; + + /// + /// Constructor + /// + /// The name. + /// The display text. + /// The tag or null + public UriPathSegmentRoot(string display = null, object tag = null) + { + Value = "/"; + Display = display; + Tag = tag; + } + + /// + /// Checks whether the node matches the path element. + /// + /// The value to check. + /// True if the path element matched, false otherwise. + public bool IsMatched(string value) + { + if (string.IsNullOrWhiteSpace(value)) + { + return false; + } + + return Value.Equals(value, StringComparison.OrdinalIgnoreCase) || + (Value + "/").Equals(value, StringComparison.OrdinalIgnoreCase); + } + + /// + /// Make a deep copy. + /// + /// The copy. + public virtual IUriPathSegment Copy() + { + return new UriPathSegmentRoot(Display, Tag); + } + + /// + /// Compare the object. + /// + /// The comparison object. + /// true if equals, false otherwise + public virtual bool Equals(IUriPathSegment obj) + { + if (obj == null) + { + return false; + } + + return obj is UriPathSegmentRoot segment; + } + + /// + /// Returns or sets the display text. + /// + /// The culture. + public virtual string GetDisplay(CultureInfo culture) + { + return InternationalizationManager.I18N(culture, Display); + } + + /// + /// Converts the segment to a string. + /// + /// A string that represents the current segment. + public override string ToString() + { + return Value ?? ""; + } + } +} \ No newline at end of file diff --git a/src/WebExpress/WebUri/UriPathSegmentVariable.cs b/src/WebExpress/WebUri/UriPathSegmentVariable.cs new file mode 100644 index 0000000..2644501 --- /dev/null +++ b/src/WebExpress/WebUri/UriPathSegmentVariable.cs @@ -0,0 +1,146 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Text.RegularExpressions; +using WebExpress.Internationalization; + +namespace WebExpress.WebUri +{ + /// + /// Variable path segment. + /// + public abstract class UriPathSegmentVariable : IUriPathSegmentVariable + { + /// + /// Returns or sets the id. + /// + public string Id => VariableName?.ToLower(); + + /// + /// Returns or sets the variable name. + /// + public string VariableName { get; set; } + + /// + /// Returns or sets the path text. + /// + public string Value { get; set; } + + /// + /// Returns or sets the display text. + /// + public string Display { get; set; } + + /// + /// Returns or sets the regex expression. + /// + public string Expression { get; protected set; } + + /// + /// Returns or sets the tag. + /// + public object Tag { get; set; } + + /// + /// Checks for empty path segment. + /// + public bool IsEmpty => string.IsNullOrWhiteSpace(VariableName) || VariableName.Equals("/"); + + /// + /// Constructor + /// + /// The name. + /// The tag or null + public UriPathSegmentVariable(string name, object tag = null) + : this(name, null, tag) + { + } + + /// + /// Constructor + /// + /// The name. + /// The display text. + /// The tag or null + public UriPathSegmentVariable(string name, string display, object tag = null) + { + VariableName = name; + Display = display; + Tag = tag; + } + + /// + /// Returns the variable. + /// + /// The value. + /// The variable value pair. + public abstract IDictionary GetVariable(string value); + + /// + /// Checks whether the node matches the path element. + /// + /// The value to check. + /// True if the path element matched, false otherwise. + public bool IsMatched(string value) + { + if (string.IsNullOrWhiteSpace(value)) + { + return false; + } + else if (string.IsNullOrWhiteSpace(Expression) && Value.Equals(value, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + else if (Regex.IsMatch(value, Expression, RegexOptions.IgnoreCase)) + { + return true; + } + + return false; + } + + /// + /// Make a deep copy. + /// + /// The copy. + public abstract IUriPathSegment Copy(); + + /// + /// Compare the object. + /// + /// The comparison object. + /// true if equals, false otherwise + public virtual bool Equals(IUriPathSegment obj) + { + if (obj == null) + { + return false; + } + else if (obj is UriPathSegmentVariable segment) + { + return VariableName.Equals(segment.VariableName, StringComparison.OrdinalIgnoreCase) && + Expression.Equals(segment.Expression); + } + + return false; + } + + /// + /// Returns or sets the display text. + /// + /// The culture. + public virtual string GetDisplay(CultureInfo culture) + { + return string.Format(InternationalizationManager.I18N(culture, Display), Value); + } + + /// + /// Converts the segment to a string. + /// + /// A string that represents the current segment. + public override string ToString() + { + return Value ?? $"${{{VariableName}}}"; + } + } +} \ No newline at end of file diff --git a/src/WebExpress/WebUri/UriPathSegmentVariableDouble.cs b/src/WebExpress/WebUri/UriPathSegmentVariableDouble.cs new file mode 100644 index 0000000..69b25cc --- /dev/null +++ b/src/WebExpress/WebUri/UriPathSegmentVariableDouble.cs @@ -0,0 +1,79 @@ +using System.Collections.Generic; + +namespace WebExpress.WebUri +{ + /// + /// Variable path segment. + /// + public class UriPathSegmentVariableDouble : UriPathSegmentVariable + { + /// + /// Constructor + /// + /// The path text. + /// The tag or null + public UriPathSegmentVariableDouble(string name, object tag = null) + : base(name, tag) + { + VariableName = name; + Value = name; + Display = name; + Expression = @"^[+-]?(\d*,\d+|\d+(,\d*)?)( +[eE][+-]?\d+)?$"; + Tag = tag; + } + + /// + /// Constructor + /// + /// The path text. + /// The display text. + /// The tag or null + public UriPathSegmentVariableDouble(string name, string display, object tag = null) + : base(name, tag) + { + VariableName = name; + Value = name; + Display = display; + Expression = @"^[+-]?(\d*,\d+|\d+(,\d*)?)( +[eE][+-]?\d+)?$"; + Tag = tag; + } + + /// + /// Constructor + /// + /// The path segment to copy. + public UriPathSegmentVariableDouble(UriPathSegmentVariableDouble segment) + : base(segment.VariableName, segment.Display, segment.Tag) + { + Expression = segment.Expression; + } + + /// + /// Returns the variable. + /// + /// The value. + /// The variable value pair. + public override IDictionary GetVariable(string value) + { + return new Dictionary(); + } + + /// + /// Make a deep copy. + /// + /// The copy. + public override IUriPathSegment Copy() + { + return new UriPathSegmentVariableDouble(this) { Value = Value }; + } + + /// + /// Converts the segment to a string. + /// + /// A string that represents the current segment. + public override string ToString() + { + return base.ToString(); + } + } +} \ No newline at end of file diff --git a/src/WebExpress/WebUri/UriPathSegmentVariableGuid.cs b/src/WebExpress/WebUri/UriPathSegmentVariableGuid.cs new file mode 100644 index 0000000..511f608 --- /dev/null +++ b/src/WebExpress/WebUri/UriPathSegmentVariableGuid.cs @@ -0,0 +1,131 @@ +using System.Collections.Generic; +using System.Globalization; +using System.Text.RegularExpressions; +using WebExpress.Internationalization; + +namespace WebExpress.WebUri +{ + /// + /// Variable path segment. + /// + public class UriPathSegmentVariableGuid : UriPathSegmentVariable + { + /// + /// The display formats of the guid. + /// + public enum Format { Full, Simple } + + /// + /// Returns the display format. + /// + public Format DisplayFormat { get; private set; } + + /// + /// Constructor + /// + /// The path text. + /// The tag or null + public UriPathSegmentVariableGuid(string name, object tag = null) + : this(name, null, tag) + { + } + + /// + /// Constructor + /// + /// The path text. + /// The display text. + /// The tag or null + public UriPathSegmentVariableGuid(string name, string display, object tag = null) + : this(name, display, Format.Full, tag) + { + } + + /// + /// Constructor + /// + /// The path text. + /// The display text. + /// The display format. + /// The tag or null + public UriPathSegmentVariableGuid(string name, string display, Format displayFormat, object tag = null) + : base(name, display, tag) + { + VariableName = name; + DisplayFormat = displayFormat; + Expression = @"^(\{){0,1}(([0-9a-fA-F]{8})\-([0-9a-fA-F]{4})\-([0-9a-fA-F]{4})\-([0-9a-fA-F]{4})\-([0-9a-fA-F]{12}))(\}){0,1}$"; + } + + /// + /// Constructor + /// + /// The path segment to copy. + public UriPathSegmentVariableGuid(UriPathSegmentVariableGuid segment) + : base(segment.VariableName, segment.Display, segment.Tag) + { + DisplayFormat = segment.DisplayFormat; + Expression = segment.Expression; + } + + /// + /// Returns the variable. + /// + /// The value. + /// The variable value pair. + public override IDictionary GetVariable(string value) + { + var match = Regex.Match(value, Expression, RegexOptions.IgnoreCase | RegexOptions.Compiled); + + if (match.Success) + { + var dict = new Dictionary + { + { VariableName, match.Groups[2].ToString() } + }; + + return dict; + } + + return new Dictionary(); + } + + /// + /// Make a deep copy. + /// + /// The copy. + public override IUriPathSegment Copy() + { + return new UriPathSegmentVariableGuid(this) { Value = Value }; + } + + /// + /// Returns or sets the display text. + /// + /// The culture. + public override string GetDisplay(CultureInfo culture) + { + var match = Regex.Match(Value, Expression, RegexOptions.IgnoreCase | RegexOptions.Compiled); + var guid = DisplayFormat == Format.Simple ? match.Groups[7].ToString() : match.Groups[2].ToString(); + + if (string.IsNullOrWhiteSpace(Display) || !Display.Contains("{0}")) + { + return guid; + } + + return string.Format + ( + InternationalizationManager.I18N(culture, Display), + guid + ); + } + + /// + /// Converts the segment to a string. + /// + /// A string that represents the current segment. + public override string ToString() + { + return base.ToString(); + } + } +} \ No newline at end of file diff --git a/src/WebExpress/WebUri/UriPathSegmentVariableInt.cs b/src/WebExpress/WebUri/UriPathSegmentVariableInt.cs new file mode 100644 index 0000000..df43496 --- /dev/null +++ b/src/WebExpress/WebUri/UriPathSegmentVariableInt.cs @@ -0,0 +1,79 @@ +using System.Collections.Generic; + +namespace WebExpress.WebUri +{ + /// + /// Variable path segment. + /// + public class UriPathSegmentVariableInt : UriPathSegmentVariable + { + /// + /// Constructor + /// + /// The path text. + /// The tag or null + public UriPathSegmentVariableInt(string name, object tag = null) + : base(name, tag) + { + VariableName = name; + Value = name; + Display = name; + Expression = @"^[+-]*\d$"; + Tag = tag; + } + + /// + /// Constructor + /// + /// The path text. + /// The display text. + /// The tag or null + public UriPathSegmentVariableInt(string name, string display, object tag = null) + : base(name, tag) + { + VariableName = name; + Value = name; + Display = display; + Expression = @"^[+-]*\d$"; + Tag = tag; + } + + /// + /// Constructor + /// + /// The path segment to copy. + public UriPathSegmentVariableInt(UriPathSegmentVariableInt segment) + : base(segment.VariableName, segment.Display, segment.Tag) + { + Expression = segment.Expression; + } + + /// + /// Returns the variable. + /// + /// The value. + /// The variable value pair. + public override IDictionary GetVariable(string value) + { + return new Dictionary(); + } + + /// + /// Make a deep copy. + /// + /// The copy. + public override IUriPathSegment Copy() + { + return new UriPathSegmentVariableInt(this) { Value = Value }; + } + + /// + /// Converts the segment to a string. + /// + /// A string that represents the current segment. + public override string ToString() + { + return base.ToString(); + } + } +} \ No newline at end of file diff --git a/src/WebExpress/WebUri/UriPathSegmentVariableString.cs b/src/WebExpress/WebUri/UriPathSegmentVariableString.cs new file mode 100644 index 0000000..135ab53 --- /dev/null +++ b/src/WebExpress/WebUri/UriPathSegmentVariableString.cs @@ -0,0 +1,79 @@ +using System.Collections.Generic; + +namespace WebExpress.WebUri +{ + /// + /// Variable path segment. + /// + public class UriPathSegmentVariableString : UriPathSegmentVariable + { + /// + /// Constructor + /// + /// The path text. + /// The tag or null + public UriPathSegmentVariableString(string name, object tag = null) + : base(name, tag) + { + VariableName = name; + Value = name; + Display = name; + Expression = "^[^\"]*$"; + Tag = tag; + } + + /// + /// Constructor + /// + /// The path text. + /// The display text. + /// The tag or null + public UriPathSegmentVariableString(string name, string display, object tag = null) + : base(name, tag) + { + VariableName = name; + Value = name; + Display = display; + Expression = "^[^\"]*$"; + Tag = tag; + } + + /// + /// Constructor + /// + /// The path segment to copy. + public UriPathSegmentVariableString(UriPathSegmentVariableString segment) + : base(segment.VariableName, segment.Display, segment.Tag) + { + Expression = segment.Expression; + } + + /// + /// Returns the variable. + /// + /// The value. + /// The variable value pair. + public override IDictionary GetVariable(string value) + { + return new Dictionary(); + } + + /// + /// Make a deep copy. + /// + /// The copy. + public override IUriPathSegment Copy() + { + return new UriPathSegmentVariableString(this) { Value = Value }; + } + + /// + /// Converts the segment to a string. + /// + /// A string that represents the current segment. + public override string ToString() + { + return base.ToString(); + } + } +} \ No newline at end of file diff --git a/src/WebExpress/WebUri/UriPathSegmentVariableUInt.cs b/src/WebExpress/WebUri/UriPathSegmentVariableUInt.cs new file mode 100644 index 0000000..d6c9e70 --- /dev/null +++ b/src/WebExpress/WebUri/UriPathSegmentVariableUInt.cs @@ -0,0 +1,79 @@ +using System.Collections.Generic; + +namespace WebExpress.WebUri +{ + /// + /// Variable path segment. + /// + public class UriPathSegmentVariableUInt : UriPathSegmentVariable + { + /// + /// Constructor + /// + /// The path text. + /// The tag or null + public UriPathSegmentVariableUInt(string name, object tag = null) + : base(name, tag) + { + VariableName = name; + Value = name; + Display = name; + Expression = @"^\d$"; + Tag = tag; + } + + /// + /// Constructor + /// + /// The path text. + /// The display text. + /// The tag or null + public UriPathSegmentVariableUInt(string name, string display, object tag = null) + : base(name, display, tag) + { + VariableName = name; + Value = name; + Display = display; + Expression = @"^\d$"; + Tag = tag; + } + + /// + /// Constructor + /// + /// The path segment to copy. + public UriPathSegmentVariableUInt(UriPathSegmentVariableUInt segment) + : base(segment.VariableName, segment.Display, segment.Tag) + { + Expression = segment.Expression; + } + + /// + /// Returns the variable. + /// + /// The value. + /// The variable value pair. + public override IDictionary GetVariable(string value) + { + return new Dictionary(); + } + + /// + /// Make a deep copy. + /// + /// The copy. + public override IUriPathSegment Copy() + { + return new UriPathSegmentVariableUInt(this) { Value = Value }; + } + + /// + /// Converts the segment to a string. + /// + /// A string that represents the current segment. + public override string ToString() + { + return base.ToString(); + } + } +} \ No newline at end of file diff --git a/src/WebExpress/WebUri/UriQuerry.cs b/src/WebExpress/WebUri/UriQuerry.cs new file mode 100644 index 0000000..8022261 --- /dev/null +++ b/src/WebExpress/WebUri/UriQuerry.cs @@ -0,0 +1,29 @@ +namespace WebExpress.WebUri +{ + /// + /// The query part (e.g. ?title=Uniform_Resource_Identifier&action=submit). + /// + public class UriQuerry + { + /// + /// Returns the key. + /// + public string Key { get; protected set; } + + /// + /// Returns the value. + /// + public string Value { get; protected set; } + + /// + /// Constructor + /// + /// The key. + /// The value. + public UriQuerry(string key, string value) + { + Key = key; + Value = value; + } + } +} \ No newline at end of file diff --git a/src/WebExpress/WebUri/UriResource.cs b/src/WebExpress/WebUri/UriResource.cs new file mode 100644 index 0000000..20cfcd7 --- /dev/null +++ b/src/WebExpress/WebUri/UriResource.cs @@ -0,0 +1,532 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; + +namespace WebExpress.WebUri +{ + /// + /// A resource uri (e.g. /image.png). + /// + public class UriResource + { + /// + /// The scheme (e.g. Http, FTP). + /// + public UriScheme Scheme { get; set; } = UriScheme.Http; + + /// + /// The authority (e.g. user@example.com:8080). + /// + public UriAuthority Authority { get; set; } + + /// + /// The path (e.g. /over/there). + /// + public ICollection PathSegments { get; } = new List(); + + /// + /// Returns the extended path. The extended path is the postfix of the resource's path. + /// + public UriResource ExtendedPath + { + get + { + return new UriResource(Skip(ResourceRoot.PathSegments.Count()).PathSegments?.ToArray()); + } + } + + /// + /// The query part (e.g. ?title=Uniform_Resource_Identifier&action=submit). + /// + public ICollection Query { get; } = new List(); + + /// + /// References a position within a resource (e.g. #Anchor). + /// + public string Fragment { get; set; } + + /// + /// Returns the display string of the Uri + /// + public virtual string Display + { + get + { + if (PathSegments.LastOrDefault() is IUriPathSegment last) + { + return last?.Display; + } + + return null; + } + + set + { + if (PathSegments.LastOrDefault() is IUriPathSegment last) + { + last.Display = value; + } + } + } + + /// + /// Determines if the uri is empty. + /// + 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. + /// + public bool IsRoot => PathSegments.Count() == 1; + + /// + /// Checks if it is a relative uri. + /// + public bool IsRelative => Authority == null; + + /// + /// Returns the variables. + /// + public Dictionary Parameters + { + get + { + var dic = new Dictionary(); + + foreach (var path in PathSegments) + { + if (path is IUriPathSegmentVariable variable) + { + if (!dic.ContainsKey(variable.VariableName?.ToLower())) + { + dic.Add(variable.VariableName?.ToLower(), variable.Value); + } + } + } + + return dic; + } + } + + /// + /// Constructor + /// + public UriResource() + { + + } + + /// + /// Constructor + /// + /// The scheme (e.g. Http, FTP). + /// The authority (e.g. user@example.com:8080). + /// The uri. + public UriResource(UriScheme scheme, UriAuthority authority, string uri) + : this(uri) + { + Scheme = scheme; + Authority = authority; + } + + /// + /// Constructor + /// + /// The uri. + public UriResource(string uri) + { + if (uri == null) return; + + if (Enum.GetNames(typeof(UriScheme)).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*))?(.*)?)$"); + + try + { + Scheme = (UriScheme)Enum.Parse(typeof(UriScheme), match.Groups[1].Value, true); + } + catch + { + Scheme = UriScheme.Http; + } + + Authority = new UriAuthority() + { + User = match.Groups[2].Success ? match.Groups[2].Value : null, + Host = match.Groups[3].Success ? match.Groups[3].Value : null, + Port = match.Groups[4].Success ? Convert.ToInt32(match.Groups[4].Value) : null + }; + + uri = match.Groups[5].Value; + + } + + var relativeMatch = Regex.Match(uri, @"^(\/([a-zA-Z0-9-+*%()=._/$]*))(#([a-zA-Z0-9-+*%()=._/$]*))?(\?(.*))?$"); + + PathSegments.Add(new UriPathSegmentRoot()); + + foreach (var p in relativeMatch.Groups[2].Value.Split('/', StringSplitOptions.RemoveEmptyEntries)) + { + PathSegments.Add(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()) + { + var item = q.Split('='); + + Query.Add(new UriQuerry(item[0], item.Length > 1 ? item[1] : null)); + } + } + + /// + /// Copy constructor + /// + /// The uri. + public UriResource(UriResource 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; + } + + /// + /// Constructor + /// + /// The path segments. + public UriResource(params IUriPathSegment[] segments) + { + PathSegments.Add(new UriPathSegmentRoot()); + + foreach (var segment in segments.Where(x => !(x is UriPathSegmentRoot))) + { + PathSegments.Add(segment); + } + } + + /// + /// Constructor + /// + /// The uri. + /// The path segments. + public UriResource(UriResource 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 + /// + /// The uri. + /// The path segments. + /// Other segments. + public UriResource(UriResource 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 + /// + /// 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 path segments. + public UriResource(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(); + Fragment = fragment; + } + + /// + /// Adds a path element. + /// + /// The path to append. + /// The extended path. + public virtual UriResource Append(string path) + { + if (string.IsNullOrWhiteSpace(path)) + { + return this; + } + + var copy = new UriResource(this); + + foreach (var p in path.Split('/', StringSplitOptions.RemoveEmptyEntries)) + { + copy.PathSegments.Add(new UriPathSegmentConstant(p)); + } + + return copy; + } + + /// + /// Adds a path element. + /// + /// The path to append. + /// The extended path. + public virtual UriResource Append(IUriPathSegment path) + { + if (path == null || path.IsEmpty) + { + return this; + } + + var copy = new UriResource(this); + + copy.PathSegments.Add(path); + + return copy; + } + + /// + /// Return a shortened uri containing n-elements. + /// count > 0 count elements are included + /// count < 0 count elements are truncated + /// count = 0 an empty uri is returned + /// + /// The count. + /// The sub uri. + public virtual UriResource Take(int count) + { + var copy = new UriResource(this); + var path = copy.PathSegments.ToList(); + copy.PathSegments.Clear(); + + if (count == 0) + { + return new UriResource(); + } + else if (count > 0) + { + (copy.PathSegments as List).AddRange(path.Take(count)); + } + else if (count < 0 && Math.Abs(count) < path.Count) + { + (copy.PathSegments as List).AddRange(path.Take(path.Count + count)); + } + else + { + return null; + } + + return copy; + } + + /// + /// Return a shortened uri by not including the first n elements. + /// count > 0 count elements are skipped + /// count <= 0 an empty Uri is returned + /// + /// The count. + /// The sub uri. + public UriResource Skip(int count) + { + if (count >= PathSegments.Count) + { + return null; + } + if (count > 0) + { + var copy = new UriResource(this); + var path = copy.PathSegments.ToList(); + copy.PathSegments.Clear(); + (copy.PathSegments as List).AddRange(path.Skip(count)); + + return copy; + } + + return new UriResource(this); + } + + /// + /// Determines whether the given segment is part of the uri. + /// + /// The segment to be tested. + /// true if successful, false otherwise. + public virtual bool Contains(string segment) + { + return PathSegments.Where(x => x.Value.Equals(segment, StringComparison.OrdinalIgnoreCase)).Any(); + } + + /// + /// Checks whether a given uri is part of that uri. + /// + /// The Uri to be checked. + /// true if part of the uri, false otherwise. + public bool StartsWith(UriResource uri) + { + return ToString().StartsWith(uri.ToString()); + } + + /// + /// Creates a new resource 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) + { + var pathSegments = PathSegments.AsEnumerable(); + + foreach (var parameter in parameters) + { + pathSegments = pathSegments.Select(x => + { + if (x is IUriPathSegmentVariable variable && + variable.VariableName.Equals(parameter?.Key, StringComparison.OrdinalIgnoreCase)) + { + var copy = variable.Copy() as IUriPathSegmentVariable; + copy.Value = parameter.Value; + + return copy; + } + + return x; + }); + } + + 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), + }; + } + + /// + /// Combines the specified uris into a compound uri. + /// + /// The uris to be combine. + /// A combined uri. + public static UriResource Combine(params string[] uris) + { + var uri = new UriResource(); + + (uri.PathSegments as List).AddRange(uris.Where(x => !string.IsNullOrWhiteSpace(x)) + .SelectMany(x => x.Split('/', StringSplitOptions.RemoveEmptyEntries)) + .Select(x => new UriPathSegmentConstant(x) as IUriPathSegment)); + + return uri; + } + + /// + /// Combines the specified uris into a compound uri. + /// + /// The uris to be combine. + /// A combined uri. + public static UriResource Combine(params UriResource[] uris) + { + var uri = new UriResource(uris.FirstOrDefault()); + (uri.PathSegments as List).AddRange(uris.Skip(1).SelectMany(x => x.PathSegments.Skip(1))); + + return uri; + } + + /// + /// Combines the specified uris into a compound uri. + /// + /// The first uri to be combine. + /// The uris to be combine. + /// A combined uri. + public static UriResource Combine(UriResource 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)); + return copy; + } + + /// + /// Converts a resource uri to a normal uri. + /// + /// The uri to convert. + public static implicit operator string(UriResource uri) + { + return uri?.ToString(); + } + } +} \ No newline at end of file diff --git a/src/WebExpress/WebUri/UriScheme.cs b/src/WebExpress/WebUri/UriScheme.cs new file mode 100644 index 0000000..b6773ed --- /dev/null +++ b/src/WebExpress/WebUri/UriScheme.cs @@ -0,0 +1,16 @@ +namespace WebExpress.WebUri +{ + /// + /// The typ of the uri. + /// + public enum UriScheme + { + File, + FTP, + Http, + Https, + Ldap, + Ldaps, + Mailto + } +} \ No newline at end of file From 5683760633e164b5e7180badbeb51d0a13075c32 Mon Sep 17 00:00:00 2001 From: ReneSchwarzer Date: Wed, 1 Nov 2023 18:22:56 +0100 Subject: [PATCH 2/6] bug fixes --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 83e5a5b..5f5d076 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -![WebExpress logo](https://raw.githubusercontent.com/ReneSchwarzer/WebExpress/icon.png) +![WebExpress logo](https://raw.githubusercontent.com/ReneSchwarzer/WebExpress.Doc/main/icon.png) # WebExpress WebExpress is a lightweight web server optimized for use in low-performance environments (e.g. Rasperry PI). By providing @@ -16,5 +16,5 @@ language (e.g. C#). Some advantages of WebExpress are: # Establish see https://github.com/ReneSchwarzer/WebExpress.Doc/blob/main/doc/installation_guide.md and https://github.com/ReneSchwarzer/WebExpress.Doc/blob/main/doc/development_guide.md -#Tags +# Tags #Raspberry #Raspbian #IoT #NETCore #WebExpress \ No newline at end of file From ae45ec25c37f8c07975f935422df1b06dc598f75 Mon Sep 17 00:00:00 2001 From: ReneSchwarzer Date: Sun, 5 Nov 2023 10:48:03 +0100 Subject: [PATCH 3/6] bug fixes --- src/WebExpress/HttpServer.cs | 5 +++++ src/WebExpress/WebSitemap/SitemapManager.cs | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/WebExpress/HttpServer.cs b/src/WebExpress/HttpServer.cs index e16a860..9d0fbca 100644 --- a/src/WebExpress/HttpServer.cs +++ b/src/WebExpress/HttpServer.cs @@ -481,6 +481,11 @@ private async Task SendResponseAsync(HttpContext context, Response response) searchResult?.ApplicationContext?.PluginContext ); + if (statusPage == null) + { + return response; + } + if (statusPage is II18N i18n) { i18n.Culture = culture; diff --git a/src/WebExpress/WebSitemap/SitemapManager.cs b/src/WebExpress/WebSitemap/SitemapManager.cs index 89dd6ce..378d9a3 100644 --- a/src/WebExpress/WebSitemap/SitemapManager.cs +++ b/src/WebExpress/WebSitemap/SitemapManager.cs @@ -384,7 +384,7 @@ SearchContext searchContext Instance = CreateInstance(node, new UriResource(outPathSegments.ToArray()), searchContext), }; } - else if (node.IsLeaf && nextPathSegment != null && node.ResourceItem.IncludeSubPaths && node.ResourceItem != null) + else if (node.IsLeaf && nextPathSegment != null && node.ResourceItem != null && node.ResourceItem.IncludeSubPaths) { return new SearchResult() { From 7356bbb40a480dce165c25a214344281245cf17b Mon Sep 17 00:00:00 2001 From: ReneSchwarzer Date: Sun, 5 Nov 2023 10:50:02 +0100 Subject: [PATCH 4/6] added tests --- src/WebExpress.Test/Schedule/UnitTestClock.cs | 239 ++++++++++++++++++ src/WebExpress.Test/Schedule/UnitTestCron.cs | 168 ++++++++++++ .../Uri/UnitTestUriAbsolute.cs | 139 ++++++++++ .../Uri/UnitTestUriRelative.cs | 105 ++++++++ .../Uri/UnitTestUriRelativeAppend.cs | 49 ++++ .../Uri/UnitTestUriRelativeExtendedPath.cs | 46 ++++ .../Uri/UnitTestUriRelativeSkip.cs | 79 ++++++ .../Uri/UnitTestUriRelativeTake.cs | 134 ++++++++++ src/WebExpress/WebExpress.csproj | 6 +- 9 files changed, 962 insertions(+), 3 deletions(-) create mode 100644 src/WebExpress.Test/Schedule/UnitTestClock.cs create mode 100644 src/WebExpress.Test/Schedule/UnitTestCron.cs create mode 100644 src/WebExpress.Test/Uri/UnitTestUriAbsolute.cs create mode 100644 src/WebExpress.Test/Uri/UnitTestUriRelative.cs create mode 100644 src/WebExpress.Test/Uri/UnitTestUriRelativeAppend.cs create mode 100644 src/WebExpress.Test/Uri/UnitTestUriRelativeExtendedPath.cs create mode 100644 src/WebExpress.Test/Uri/UnitTestUriRelativeSkip.cs create mode 100644 src/WebExpress.Test/Uri/UnitTestUriRelativeTake.cs diff --git a/src/WebExpress.Test/Schedule/UnitTestClock.cs b/src/WebExpress.Test/Schedule/UnitTestClock.cs new file mode 100644 index 0000000..3729f22 --- /dev/null +++ b/src/WebExpress.Test/Schedule/UnitTestClock.cs @@ -0,0 +1,239 @@ +using System; +using System.Linq; +using WebExpress.WebJob; +using Xunit; + +namespace WebExpress.Test.Schedule +{ + /// + /// Tests the scheduler's clock. + /// + public class UnitTestClock + { + [Fact] + public void Synchronize_1() + { + var dateTime = DateTime.Now; + + var clock = new Clock(DateTime.Now.AddMinutes(-5)); + + var elapsed = clock.Synchronize(); + + Assert.True + ( + elapsed.Count() == 5 + ); + } + + [Fact] + public void Synchronize_2() + { + var dateTime = DateTime.Now; + + var clock = new Clock(DateTime.Now.AddDays(-1)); + + 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 + ); + } + + [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)); + clock1.Tick(); + + + Assert.True + ( + clock1 == clock2 + ); + } + } +} diff --git a/src/WebExpress.Test/Schedule/UnitTestCron.cs b/src/WebExpress.Test/Schedule/UnitTestCron.cs new file mode 100644 index 0000000..7bacdfa --- /dev/null +++ b/src/WebExpress.Test/Schedule/UnitTestCron.cs @@ -0,0 +1,168 @@ +using System; +using System.Globalization; +using WebExpress.WebComponent; +using WebExpress.WebJob; +using Xunit; + +namespace WebExpress.Test.Schedule +{ + /// + /// Test the cron job of the scheduler. + /// + 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); + + 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) + ); + } + + [Fact] + public void Create_2() + { + var context = new HttpServerContext(null, null, null, null, null, null, null, CultureInfo.CurrentCulture, Log.Current, null); + 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) + ); + } + + [Fact] + public void Create_3() + { + var context = new HttpServerContext(null, null, null, null, null, null, null, CultureInfo.CurrentCulture, Log.Current, null); + 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) + ); + } + + [Fact] + public void Create_4() + { + var context = new HttpServerContext(null, null, null, null, null, null, null, CultureInfo.CurrentCulture, Log.Current, null); + 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 + ); + } + + [Fact] + public void Create_5() + { + var context = new HttpServerContext(null, null, null, null, null, null, null, CultureInfo.CurrentCulture, Log.Current, null); + 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) + ); + } + + [Fact] + public void Create_6() + { + var context = new HttpServerContext(null, null, null, null, null, null, null, CultureInfo.CurrentCulture, Log.Current, null); + 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 + ); + } + + [Fact] + public void Matching_1() + { + var context = new HttpServerContext(null, null, null, null, null, null, null, CultureInfo.CurrentCulture, Log.Current, null); + 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) + ); + } + + [Fact] + public void Matching_2() + { + var context = new HttpServerContext(null, null, null, null, null, null, null, CultureInfo.CurrentCulture, Log.Current, null); + 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) + ); + } + + [Fact] + public void Matching_3() + { + var context = new HttpServerContext(null, null, null, null, null, null, null, CultureInfo.CurrentCulture, Log.Current, null); + 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) + ); + } + } +} diff --git a/src/WebExpress.Test/Uri/UnitTestUriAbsolute.cs b/src/WebExpress.Test/Uri/UnitTestUriAbsolute.cs new file mode 100644 index 0000000..e3aa560 --- /dev/null +++ b/src/WebExpress.Test/Uri/UnitTestUriAbsolute.cs @@ -0,0 +1,139 @@ +using System.Collections.Generic; +using System.Linq; +using WebExpress.WebUri; +using Xunit; + +namespace WebExpress.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.Test/Uri/UnitTestUriRelative.cs b/src/WebExpress.Test/Uri/UnitTestUriRelative.cs new file mode 100644 index 0000000..9034d6e --- /dev/null +++ b/src/WebExpress.Test/Uri/UnitTestUriRelative.cs @@ -0,0 +1,105 @@ +using System.Linq; +using WebExpress.WebUri; +using Xunit; + +namespace WebExpress.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.Test/Uri/UnitTestUriRelativeAppend.cs b/src/WebExpress.Test/Uri/UnitTestUriRelativeAppend.cs new file mode 100644 index 0000000..66c559c --- /dev/null +++ b/src/WebExpress.Test/Uri/UnitTestUriRelativeAppend.cs @@ -0,0 +1,49 @@ +using WebExpress.WebUri; +using Xunit; + +namespace WebExpress.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.Test/Uri/UnitTestUriRelativeExtendedPath.cs b/src/WebExpress.Test/Uri/UnitTestUriRelativeExtendedPath.cs new file mode 100644 index 0000000..5e2453f --- /dev/null +++ b/src/WebExpress.Test/Uri/UnitTestUriRelativeExtendedPath.cs @@ -0,0 +1,46 @@ +using System.Collections.Generic; +using WebExpress.WebUri; +using Xunit; + +namespace WebExpress.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.Test/Uri/UnitTestUriRelativeSkip.cs b/src/WebExpress.Test/Uri/UnitTestUriRelativeSkip.cs new file mode 100644 index 0000000..89c1b2c --- /dev/null +++ b/src/WebExpress.Test/Uri/UnitTestUriRelativeSkip.cs @@ -0,0 +1,79 @@ +using WebExpress.WebUri; +using Xunit; + +namespace WebExpress.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.Test/Uri/UnitTestUriRelativeTake.cs b/src/WebExpress.Test/Uri/UnitTestUriRelativeTake.cs new file mode 100644 index 0000000..c63af8b --- /dev/null +++ b/src/WebExpress.Test/Uri/UnitTestUriRelativeTake.cs @@ -0,0 +1,134 @@ +using WebExpress.WebUri; +using Xunit; + +namespace WebExpress.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/WebExpress.csproj b/src/WebExpress/WebExpress.csproj index a4a44f8..2be3fad 100644 --- a/src/WebExpress/WebExpress.csproj +++ b/src/WebExpress/WebExpress.csproj @@ -3,8 +3,8 @@ Library WebExpress - 0.0.1.0 - 0.0.1.0 + 0.0.2.0 + 0.0.2.0 net7.0 any https://github.com/ReneSchwarzer/WebExpress.git @@ -14,7 +14,7 @@ true True Core library of the WebExpress web server. - 0.0.1-alpha + 0.0.2-alpha https://github.com/ReneSchwarzer/WebExpress icon.png README.md From f5d1c872d7af2ce5031bcc7567b29bc05feca049 Mon Sep 17 00:00:00 2001 From: ReneSchwarzer Date: Sun, 5 Nov 2023 15:37:33 +0100 Subject: [PATCH 5/6] change the readme --- README.md | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 5f5d076..19177af 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -![WebExpress logo](https://raw.githubusercontent.com/ReneSchwarzer/WebExpress.Doc/main/icon.png) +![WebExpress](https://raw.githubusercontent.com/ReneSchwarzer/WebExpress.Doc/main/assets/banner.png) # WebExpress WebExpress is a lightweight web server optimized for use in low-performance environments (e.g. Rasperry PI). By providing @@ -10,11 +10,22 @@ language (e.g. C#). Some advantages of WebExpress are: - 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. -# Libraries used -- https://github.com/dotnet/core (MIT) +The WebExpress family includes the following projects: -# Establish -see https://github.com/ReneSchwarzer/WebExpress.Doc/blob/main/doc/installation_guide.md and https://github.com/ReneSchwarzer/WebExpress.Doc/blob/main/doc/development_guide.md +- [WebExpress (core)](https://github.com/ReneSchwarzer/WebExpress#readme) - This project. 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.Server](https://github.com/ReneSchwarzer/WebExpress.Server#readme) - The web server for WebExpress applications. + +# Start +To get started with WebExpress, use the following links and tutorials. + +- [installation guide](https://github.com/ReneSchwarzer/WebExpress.Doc/blob/main/doc/installation_guide.md) +- [development guide](https://github.com/ReneSchwarzer/WebExpress.Doc/blob/main/doc/development_guide.md) + +## Tutorials +- [HelloWorld](https://github.com/ReneSchwarzer/WebExpress.Tutorial.HelloWorld#readme) # Tags #Raspberry #Raspbian #IoT #NETCore #WebExpress \ No newline at end of file From 92e4919223e34c66f08985b19f85db353f3cd3c8 Mon Sep 17 00:00:00 2001 From: ReneSchwarzer Date: Sun, 5 Nov 2023 15:43:05 +0100 Subject: [PATCH 6/6] version 0.0.3 --- src/WebExpress/WebExpress.csproj | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/WebExpress/WebExpress.csproj b/src/WebExpress/WebExpress.csproj index 2be3fad..ce8ec0d 100644 --- a/src/WebExpress/WebExpress.csproj +++ b/src/WebExpress/WebExpress.csproj @@ -3,8 +3,8 @@ Library WebExpress - 0.0.2.0 - 0.0.2.0 + 0.0.3.0 + 0.0.3.0 net7.0 any https://github.com/ReneSchwarzer/WebExpress.git @@ -14,7 +14,7 @@ true True Core library of the WebExpress web server. - 0.0.2-alpha + 0.0.3-alpha https://github.com/ReneSchwarzer/WebExpress icon.png README.md