add simpleRestServices

This commit is contained in:
SuhorukovAnton 2021-05-18 12:22:20 +03:00
parent ec56ae33a3
commit 9c64797eb0
60 changed files with 3567 additions and 0 deletions

View File

@ -0,0 +1,6 @@
SimpleRestServices
==================
A simple set of REST server and client helpers
Click here for <a href="https://github.com/JSIStudios/SimpleRestServices/wiki/License">License Info</a>

View File

@ -0,0 +1,11 @@
version: 1.0.{build}
init:
- git config --global core.autocrlf true
build_script:
- cd build
- powershell -Command .\build.ps1 -VisualStudioVersion "12.0" -InstallSHFB -Verbosity minimal -Logger "${env:ProgramFiles}\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll"
- cd ..
after_build:
- cd build
- powershell -Command .\appveyor-deploy-docs.ps1
- cd ..

View File

@ -0,0 +1,17 @@
$DocsPath = "..\src\Documentation\Help"
# make sure the script was run from the expected path
If (!(Test-Path $DocsPath)) {
$host.ui.WriteErrorLine('The script was run from an invalid working directory.')
Exit 1
}
function Upload-Folder() {
param([string]$LocalPath, [string]$ArtifactName)
[Reflection.Assembly]::LoadWithPartialName('System.IO.Compression.FileSystem')
[System.IO.Compression.ZipFile]::CreateFromDirectory((Join-Path (pwd) $LocalPath), (Join-Path (pwd) $ArtifactName))
Push-AppveyorArtifact (Join-Path (pwd) $ArtifactName)
}
Upload-Folder -LocalPath "$DocsPath\v4.0" -ArtifactName "docs.zip"

View File

@ -0,0 +1,133 @@
param (
[switch]$Debug,
[string]$VisualStudioVersion = "12.0",
[switch]$NoDocs,
[string]$Verbosity = "normal",
[string]$Logger,
[switch]$InstallSHFB
)
# build the solution
$SolutionPath = "..\src\SimpleRestServices.sln"
# make sure the script was run from the expected path
if (!(Test-Path $SolutionPath)) {
$host.ui.WriteErrorLine('The script was run from an invalid working directory.')
exit 1
}
. .\version.ps1
If ($Debug) {
$BuildConfig = 'Debug'
} Else {
$BuildConfig = 'Release'
}
If ($Version.Contains('-')) {
$KeyConfiguration = 'Dev'
} Else {
$KeyConfiguration = 'Final'
}
If ($NoDocs -and -not $Debug) {
$SolutionBuildConfig = $BuildConfig + 'NoDocs'
} Else {
$SolutionBuildConfig = $BuildConfig
}
# build the main project
$nuget = '..\src\.nuget\NuGet.exe'
if ($VisualStudioVersion -eq '4.0') {
$msbuild = "$env:windir\Microsoft.NET\Framework64\v4.0.30319\msbuild.exe"
} Else {
$msbuild = "${env:ProgramFiles(x86)}\MSBuild\$VisualStudioVersion\Bin\MSBuild.exe"
}
# Attempt to restore packages up to 3 times, to improve resiliency to connection timeouts and access denied errors.
$maxAttempts = 3
For ($attempt = 0; $attempt -lt $maxAttempts; $attempt++) {
&$nuget 'restore' $SolutionPath
If ($?) {
Break
} ElseIf (($attempt + 1) -eq $maxAttempts) {
$host.ui.WriteErrorLine('Failed to restore required NuGet packages, aborting!')
exit $LASTEXITCODE
}
}
If ($InstallSHFB) {
# This is the NuGet package name for the SHFB package
$SHFBPackageName = 'EWSoftware.SHFB'
# This is the version according to the NuGet package itself
$SHFBVersion = '2014.11.22.0'
$SHFBPackagePath = "..\.shfb\$SHFBPackageName.$SHFBVersion.nupkg"
If (-not (Test-Path $SHFBPackagePath)) {
If (-not (Test-Path '..\.shfb')) {
mkdir '..\.shfb'
}
# This is the release name on GitHub where the NuGet package is attached
$SHFBRelease = 'v2014.11.22.0-beta'
$SHFBInstallerSource = "https://github.com/tunnelvisionlabs/SHFB/releases/download/$SHFBRelease/$SHFBPackageName.$SHFBVersion.nupkg"
Invoke-WebRequest $SHFBInstallerSource -OutFile $SHFBPackagePath
If (-not $?) {
$host.ui.WriteErrorLine('Failed to download the SHFB NuGet package')
Exit $LASTEXITCODE
}
}
$SHFBPackages = [System.IO.Path]::GetFullPath((Join-Path (pwd) '..\.shfb'))
$SHFBPackagesUri = [System.Uri]$SHFBPackages
Echo "$nuget 'install' 'EWSoftware.SHFB' -Version $SHFBVersion -OutputDirectory '..\src\packages' -Source $SHFBPackagesUri"
&$nuget 'install' $SHFBPackageName -Version $SHFBVersion -OutputDirectory '..\src\packages' -Source $SHFBPackagesUri
If (-not $?) {
$host.ui.WriteErrorLine('Failed to install the SHFB NuGet package')
Exit $LASTEXITCODE
}
$env:SHFBROOT = [System.IO.Path]::GetFullPath((Join-Path (pwd) "..\src\packages\$SHFBPackageName.$SHFBVersion\tools"))
}
If (-not $NoDocs) {
If ((-not $env:SHFBROOT) -or (-not (Test-Path $env:SHFBROOT))) {
$host.ui.WriteErrorLine('Could not locate Sandcastle Help File Builder')
Exit 1
}
}
If ($Logger) {
$LoggerArgument = "/logger:$Logger"
}
&$msbuild '/nologo' '/m' '/nr:false' '/t:rebuild' $LoggerArgument "/verbosity:$Verbosity" "/p:Configuration=$SolutionBuildConfig" "/p:Platform=Any CPU" "/p:VisualStudioVersion=$VisualStudioVersion" "/p:KeyConfiguration=$KeyConfiguration" $SolutionPath
if (-not $?) {
$host.ui.WriteErrorLine('Build failed, aborting!')
exit $LASTEXITCODE
}
# By default, do not create a NuGet package unless the expected strong name key files were used
. .\keys.ps1
foreach ($pair in $Keys.GetEnumerator()) {
$assembly = Resolve-FullPath -Path "..\src\SimpleRestServices\bin\$($pair.Key)\$BuildConfig\SimpleRESTServices.dll"
# Run the actual check in a separate process or the current process will keep the assembly file locked
powershell -Command ".\check-key.ps1 -Assembly '$assembly' -ExpectedKey '$($pair.Value)' -Build '$($pair.Key)'"
if (-not $?) {
$host.ui.WriteErrorLine("Failed to verify strong name key for build $($pair.Key).")
Exit $LASTEXITCODE
}
}
if (-not (Test-Path 'nuget')) {
mkdir "nuget"
}
# The NuGet packages reference XML documentation which is post-processed by SHFB. If the -NoDocs flag is specified,
# these files are not created so packaging will fail.
If (-not $NoDocs) {
&$nuget 'pack' '..\src\SimpleRestServices\SimpleRestServices.nuspec' '-OutputDirectory' 'nuget' '-Prop' "Configuration=$BuildConfig" '-Version' "$Version" '-Symbols'
}

View File

@ -0,0 +1,31 @@
param(
[string]$Assembly,
[string]$ExpectedKey,
[string]$Build = $null
)
function Get-PublicKeyToken() {
param([string]$assembly = $null)
if ($assembly) {
$bytes = $null
$bytes = [System.Reflection.Assembly]::ReflectionOnlyLoadFrom($assembly).GetName().GetPublicKeyToken()
if ($bytes) {
$key = ""
for ($i=0; $i -lt $bytes.Length; $i++) {
$key += "{0:x2}" -f $bytes[$i]
}
$key
}
}
}
if (-not $Build) {
$Build = $Assembly
}
$actual = Get-PublicKeyToken -assembly $Assembly
if ($actual -ne $ExpectedKey) {
$host.ui.WriteErrorLine("Invalid publicKeyToken for '$Build'; expected '$ExpectedKey' but found '$actual'")
exit 1
}

View File

@ -0,0 +1,25 @@
# Note: these values may only change during major release
# Also, if the values change, assembly binding redirection will not work between the affected releases.
If ($Version.Contains('-')) {
# Use the development keys
$Keys = @{
'v3.5' = '579ef5a5d8b3751c'
'v4.0' = '579ef5a5d8b3751c'
}
} Else {
# Use the final release keys
$Keys = @{
'v3.5' = '541c51dcbcf0ec3c'
'v4.0' = '541c51dcbcf0ec3c'
}
}
function Resolve-FullPath() {
param([string]$Path)
[System.IO.Path]::GetFullPath((Join-Path (pwd) $Path))
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,8 @@
. .\version.ps1
If ($Version.EndsWith('-dev')) {
$host.ui.WriteErrorLine("Cannot push development version '$Version' to NuGet.")
Exit 1
}
..\src\.nuget\NuGet.exe 'push' ".\nuget\SimpleRestServices.$Version.nupkg"

View File

@ -0,0 +1 @@
$Version = "1.3.0.3-dev"

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<solution>
<add key="disableSourceControlIntegration" value="true" />
</solution>
</configuration>

Binary file not shown.

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<topic id="d4255232-eaee-4be0-baf5-c949278a0f90" revisionNumber="1">
<developerConceptualDocument xmlns="http://ddue.schemas.microsoft.com/authoring/2003/5" xmlns:xlink="http://www.w3.org/1999/xlink">
<introduction>
<para>Welcome to the SimpleRESTServices Library Reference</para>
</introduction>
<section>
<content>
<para>Select a topic from the table of contents.</para>
</content>
</section>
<relatedTopics>
<link xlink:href="c3df986e-20f6-4ba7-8825-11f5f7efcb58"/>
</relatedTopics>
</developerConceptualDocument>
</topic>

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<topic id="c3df986e-20f6-4ba7-8825-11f5f7efcb58" revisionNumber="1">
<developerConceptualDocument
xmlns="http://ddue.schemas.microsoft.com/authoring/2003/5"
xmlns:xlink="http://www.w3.org/1999/xlink">
<introduction>
<para>
Welcome to the SimpleRESTServices libary. If you have any information, tips, updates, or corrections that
you would like to see added to the guide, feel free to submit them to the author using the
<ui>Send Feedback</ui> link at the bottom of the page or the e-mail link in the page footer.
</para>
</introduction>
<relatedTopics/>
</developerConceptualDocument>
</topic>

View File

@ -0,0 +1,101 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
<PropertyGroup>
<!-- The configuration and platform will be used to determine which
assemblies to include from solution and project documentation
sources -->
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>93356b99-d84c-4291-b739-ef8ec6a9b382</ProjectGuid>
<SHFBSchemaVersion>1.9.9.0</SHFBSchemaVersion>
<!-- AssemblyName, Name, and RootNamespace are not used by SHFB but Visual
Studio adds them anyway -->
<AssemblyName>Documentation</AssemblyName>
<RootNamespace>Documentation</RootNamespace>
<Name>Documentation</Name>
<!-- SHFB properties -->
<FrameworkVersion>.NET Framework 3.5</FrameworkVersion>
<OutputPath>.\Help\v3.5\</OutputPath>
<HtmlHelpName>SimpleRESTServices</HtmlHelpName>
<Language>en-US</Language>
<DocumentationSources>
<DocumentationSource sourceFile="..\SimpleRestServices\SimpleRestServices.v3.5.csproj" />
</DocumentationSources>
<BuildAssemblerVerbosity>OnlyWarningsAndErrors</BuildAssemblerVerbosity>
<HelpFileFormat>Website</HelpFileFormat>
<IndentHtml>False</IndentHtml>
<KeepLogFile>True</KeepLogFile>
<DisableCodeBlockComponent>False</DisableCodeBlockComponent>
<CppCommentsFixup>False</CppCommentsFixup>
<CleanIntermediates>True</CleanIntermediates>
<SyntaxFilters>Standard</SyntaxFilters>
<SdkLinkTarget>Blank</SdkLinkTarget>
<RootNamespaceTitle>API Reference</RootNamespaceTitle>
<RootNamespaceContainer>True</RootNamespaceContainer>
<PresentationStyle>VS2013</PresentationStyle>
<Preliminary>False</Preliminary>
<NamingMethod>MemberName</NamingMethod>
<HelpTitle>SimpleRESTServices API Reference Documentation</HelpTitle>
<FeedbackEMailAddress>openstack.net%40lists.rackspace.com</FeedbackEMailAddress>
<ContentPlacement>AboveNamespaces</ContentPlacement>
<WebsiteSdkLinkType>Msdn</WebsiteSdkLinkType>
<HtmlSdkLinkType>Msdn</HtmlSdkLinkType>
<IncludeFavorites>True</IncludeFavorites>
<BinaryTOC>True</BinaryTOC>
<VisibleItems>Attributes, ExplicitInterfaceImplementations, InheritedMembers, InheritedFrameworkMembers, Protected, ProtectedInternalAsProtected, SealedProtected</VisibleItems>
<ComponentConfigurations>
<ComponentConfig id="IntelliSense Component" enabled="True">
<component id="IntelliSense Component">
<output includeNamespaces="false" namespacesFile="Namespaces" folder="{@OutputFolder}\..\..\Api\v3.5" />
</component>
</ComponentConfig>
</ComponentConfigurations>
<ApiFilter />
<HelpAttributes />
<NamespaceSummaries />
<PlugInConfigurations>
<PlugInConfig id="IntelliSense Only" enabled="True">
<configuration />
</PlugInConfig>
</PlugInConfigurations>
<BuildLogFile />
<HtmlHelp1xCompilerPath />
<HtmlHelp2xCompilerPath />
<WorkingPath />
<ComponentPath />
<MaximumGroupParts>2</MaximumGroupParts>
<NamespaceGrouping>False</NamespaceGrouping>
</PropertyGroup>
<!-- There are no properties for these groups. AnyCPU needs to appear in
order for Visual Studio to perform the build. The others are optional
common platform types that may appear. -->
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x64' ">
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x64' ">
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|Win32' ">
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|Win32' ">
</PropertyGroup>
<ItemGroup>
<ContentLayout Include="SimpleRestServices.content" />
</ItemGroup>
<ItemGroup>
<None Include="Content\MSHelpViewerRoot.aml" />
<None Include="Content\Welcome.aml" />
</ItemGroup>
<ItemGroup>
<Folder Include="Content\" />
</ItemGroup>
<!-- Import the SHFB build targets -->
<Import Project="$(SHFBROOT)\SandcastleHelpFileBuilder.targets" />
</Project>

View File

@ -0,0 +1,97 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
<PropertyGroup>
<!-- The configuration and platform will be used to determine which
assemblies to include from solution and project documentation
sources -->
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>7ac93313-d825-4668-a188-b7c9f21ddb37</ProjectGuid>
<SHFBSchemaVersion>1.9.9.0</SHFBSchemaVersion>
<!-- AssemblyName, Name, and RootNamespace are not used by SHFB but Visual
Studio adds them anyway -->
<AssemblyName>Documentation</AssemblyName>
<RootNamespace>Documentation</RootNamespace>
<Name>Documentation.v4.0</Name>
<!-- SHFB properties -->
<FrameworkVersion>.NET Framework 4.0</FrameworkVersion>
<OutputPath>.\Help\v4.0\</OutputPath>
<HtmlHelpName>SimpleRESTServices</HtmlHelpName>
<Language>en-US</Language>
<DocumentationSources>
<DocumentationSource sourceFile="..\SimpleRestServices\SimpleRestServices.v4.0.csproj" />
</DocumentationSources>
<BuildAssemblerVerbosity>OnlyWarningsAndErrors</BuildAssemblerVerbosity>
<HelpFileFormat>Website</HelpFileFormat>
<IndentHtml>False</IndentHtml>
<KeepLogFile>True</KeepLogFile>
<DisableCodeBlockComponent>False</DisableCodeBlockComponent>
<CppCommentsFixup>False</CppCommentsFixup>
<CleanIntermediates>True</CleanIntermediates>
<SyntaxFilters>Standard</SyntaxFilters>
<SdkLinkTarget>Blank</SdkLinkTarget>
<RootNamespaceTitle>API Reference</RootNamespaceTitle>
<RootNamespaceContainer>True</RootNamespaceContainer>
<PresentationStyle>VS2013</PresentationStyle>
<Preliminary>False</Preliminary>
<NamingMethod>MemberName</NamingMethod>
<HelpTitle>SimpleRESTServices API Reference Documentation</HelpTitle>
<FeedbackEMailAddress>openstack.net%40lists.rackspace.com</FeedbackEMailAddress>
<ContentPlacement>AboveNamespaces</ContentPlacement>
<WebsiteSdkLinkType>Msdn</WebsiteSdkLinkType>
<HtmlSdkLinkType>Msdn</HtmlSdkLinkType>
<IncludeFavorites>True</IncludeFavorites>
<BinaryTOC>True</BinaryTOC>
<VisibleItems>Attributes, ExplicitInterfaceImplementations, InheritedMembers, InheritedFrameworkMembers, Protected, ProtectedInternalAsProtected, SealedProtected</VisibleItems>
<ComponentConfigurations>
<ComponentConfig id="IntelliSense Component" enabled="True">
<component id="IntelliSense Component">
<output includeNamespaces="false" namespacesFile="Namespaces" folder="{@OutputFolder}\..\..\Api\v4.0" />
</component>
</ComponentConfig>
</ComponentConfigurations>
<ApiFilter />
<HelpAttributes />
<NamespaceSummaries />
<PlugInConfigurations />
<BuildLogFile />
<HtmlHelp1xCompilerPath />
<HtmlHelp2xCompilerPath />
<WorkingPath />
<ComponentPath />
<MaximumGroupParts>2</MaximumGroupParts>
<NamespaceGrouping>False</NamespaceGrouping>
</PropertyGroup>
<!-- There are no properties for these groups. AnyCPU needs to appear in
order for Visual Studio to perform the build. The others are optional
common platform types that may appear. -->
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x64' ">
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x64' ">
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|Win32' ">
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|Win32' ">
</PropertyGroup>
<ItemGroup>
<ContentLayout Include="SimpleRestServices.content" />
</ItemGroup>
<ItemGroup>
<None Include="Content\MSHelpViewerRoot.aml" />
<None Include="Content\Welcome.aml" />
</ItemGroup>
<ItemGroup>
<Folder Include="Content\" />
</ItemGroup>
<!-- Import the SHFB build targets -->
<Import Project="$(SHFBROOT)\SandcastleHelpFileBuilder.targets" />
</Project>

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<Topics>
<Topic id="d4255232-eaee-4be0-baf5-c949278a0f90" visible="False" isMSHVRoot="true" isSelected="true" title="SimpleRESTServices">
<HelpKeywords>
<HelpKeyword index="K" term="SimpleRESTServices" />
</HelpKeywords>
</Topic>
<Topic id="c3df986e-20f6-4ba7-8825-11f5f7efcb58" visible="True" isDefault="true" title="Welcome">
<HelpKeywords>
<HelpKeyword index="K" term="Welcome" />
</HelpKeywords>
</Topic>
</Topics>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<TestSettings name="Local" id="cb427f2e-9eeb-4819-95c3-7a7dd75880a1" xmlns="http://microsoft.com/schemas/VisualStudio/TeamTest/2010">
<Description>These are default test settings for a local test run.</Description>
<Deployment enabled="false" />
<Execution>
<TestTypeSpecific />
<AgentRule name="Execution Agents">
</AgentRule>
</Execution>
</TestSettings>

View File

@ -0,0 +1,89 @@

Microsoft Visual Studio Solution File, Format Version 11.00
# Visual Studio 2010
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SimpleRestServices.v4.0", "SimpleRestServices\SimpleRestServices.v4.0.csproj", "{4A44EB7C-06D5-43F0-B660-F684CBAAC99F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnitTests", "Testing\UnitTests\UnitTests.csproj", "{BB206928-0F2E-46A1-B438-D3ED9AF72BA1}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{D7C8177E-0A12-419B-B1BD-AEFD365BA657}"
ProjectSection(SolutionItems) = preProject
..\appveyor.yml = ..\appveyor.yml
Local.testsettings = Local.testsettings
SimpleRestServices.vsmdi = SimpleRestServices.vsmdi
TraceAndTestImpact.testsettings = TraceAndTestImpact.testsettings
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Testing", "Testing", "{F5B9B422-570A-4797-8F59-7A8C13A8934D}"
EndProject
Project("{7CF6DF6D-3B04-46F8-A40B-537D21BCA0B4}") = "Documentation.v4.0", "Documentation\Documentation.v4.0.shfbproj", "{7AC93313-D825-4668-A188-B7C9F21DDB37}"
ProjectSection(ProjectDependencies) = postProject
{4A44EB7C-06D5-43F0-B660-F684CBAAC99F} = {4A44EB7C-06D5-43F0-B660-F684CBAAC99F}
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SimpleRestServices.v3.5", "SimpleRestServices\SimpleRestServices.v3.5.csproj", "{1960D862-8AD9-48BE-9290-B7E23EA03D5C}"
EndProject
Project("{7CF6DF6D-3B04-46F8-A40B-537D21BCA0B4}") = "Documentation.v3.5", "Documentation\Documentation.v3.5.shfbproj", "{93356B99-D84C-4291-B739-EF8EC6A9B382}"
ProjectSection(ProjectDependencies) = postProject
{1960D862-8AD9-48BE-9290-B7E23EA03D5C} = {1960D862-8AD9-48BE-9290-B7E23EA03D5C}
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{A57581E8-CE20-4E4A-81AA-B6BB5611D10F}"
ProjectSection(SolutionItems) = preProject
.nuget\NuGet.Config = .nuget\NuGet.Config
.nuget\NuGet.exe = .nuget\NuGet.exe
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{377DB120-46E3-4D5C-956B-A05F9987BF6F}"
ProjectSection(SolutionItems) = preProject
..\build\appveyor-deploy-docs.ps1 = ..\build\appveyor-deploy-docs.ps1
..\build\build.ps1 = ..\build\build.ps1
..\build\check-key.ps1 = ..\build\check-key.ps1
..\build\keys.ps1 = ..\build\keys.ps1
..\build\push.ps1 = ..\build\push.ps1
..\build\version.ps1 = ..\build\version.ps1
EndProjectSection
EndProject
Global
GlobalSection(TestCaseManagementSettings) = postSolution
CategoryFile = SimpleRestServices.vsmdi
EndGlobalSection
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
ReleaseNoDocs|Any CPU = ReleaseNoDocs|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{4A44EB7C-06D5-43F0-B660-F684CBAAC99F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4A44EB7C-06D5-43F0-B660-F684CBAAC99F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4A44EB7C-06D5-43F0-B660-F684CBAAC99F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4A44EB7C-06D5-43F0-B660-F684CBAAC99F}.Release|Any CPU.Build.0 = Release|Any CPU
{4A44EB7C-06D5-43F0-B660-F684CBAAC99F}.ReleaseNoDocs|Any CPU.ActiveCfg = Release|Any CPU
{4A44EB7C-06D5-43F0-B660-F684CBAAC99F}.ReleaseNoDocs|Any CPU.Build.0 = Release|Any CPU
{BB206928-0F2E-46A1-B438-D3ED9AF72BA1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BB206928-0F2E-46A1-B438-D3ED9AF72BA1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BB206928-0F2E-46A1-B438-D3ED9AF72BA1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BB206928-0F2E-46A1-B438-D3ED9AF72BA1}.Release|Any CPU.Build.0 = Release|Any CPU
{BB206928-0F2E-46A1-B438-D3ED9AF72BA1}.ReleaseNoDocs|Any CPU.ActiveCfg = Release|Any CPU
{BB206928-0F2E-46A1-B438-D3ED9AF72BA1}.ReleaseNoDocs|Any CPU.Build.0 = Release|Any CPU
{7AC93313-D825-4668-A188-B7C9F21DDB37}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7AC93313-D825-4668-A188-B7C9F21DDB37}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7AC93313-D825-4668-A188-B7C9F21DDB37}.Release|Any CPU.Build.0 = Release|Any CPU
{7AC93313-D825-4668-A188-B7C9F21DDB37}.ReleaseNoDocs|Any CPU.ActiveCfg = Release|Any CPU
{1960D862-8AD9-48BE-9290-B7E23EA03D5C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1960D862-8AD9-48BE-9290-B7E23EA03D5C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1960D862-8AD9-48BE-9290-B7E23EA03D5C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1960D862-8AD9-48BE-9290-B7E23EA03D5C}.Release|Any CPU.Build.0 = Release|Any CPU
{1960D862-8AD9-48BE-9290-B7E23EA03D5C}.ReleaseNoDocs|Any CPU.ActiveCfg = Release|Any CPU
{1960D862-8AD9-48BE-9290-B7E23EA03D5C}.ReleaseNoDocs|Any CPU.Build.0 = Release|Any CPU
{93356B99-D84C-4291-B739-EF8EC6A9B382}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{93356B99-D84C-4291-B739-EF8EC6A9B382}.Release|Any CPU.ActiveCfg = Release|Any CPU
{93356B99-D84C-4291-B739-EF8EC6A9B382}.Release|Any CPU.Build.0 = Release|Any CPU
{93356B99-D84C-4291-B739-EF8EC6A9B382}.ReleaseNoDocs|Any CPU.ActiveCfg = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{BB206928-0F2E-46A1-B438-D3ED9AF72BA1} = {F5B9B422-570A-4797-8F59-7A8C13A8934D}
EndGlobalSection
EndGlobal

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<TestLists xmlns="http://microsoft.com/schemas/VisualStudio/TeamTest/2010">
<TestList name="Lists of Tests" id="8c43106b-9dc1-4907-a29f-aa66a61bf5b6">
<RunConfiguration id="cb427f2e-9eeb-4819-95c3-7a7dd75880a1" name="Local" storage="local.testsettings" type="Microsoft.VisualStudio.TestTools.Common.TestRunConfiguration, Microsoft.VisualStudio.QualityTools.Common, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
</TestList>
</TestLists>

View File

@ -0,0 +1,70 @@
using System;
using System.Diagnostics;
namespace JSIStudios.SimpleRESTServices.Client
{
/// <summary>
/// Represents a single header included with an HTTP response.
/// </summary>
[Serializable]
[DebuggerDisplay("{Key,nq} = {Value,nq}")]
public class HttpHeader
{
/// <summary>
/// The HTTP header key.
/// </summary>
private readonly string _key;
/// <summary>
/// The HTTP header value.
/// </summary>
private readonly string _value;
/// <summary>
/// Gets the HTTP header key.
/// </summary>
public string Key
{
get
{
return _key;
}
}
/// <summary>
/// Gets the HTTP header value.
/// </summary>
public string Value
{
get
{
return _value;
}
}
/// <summary>
/// Initializes a new instance of the <see cref="HttpHeader"/> class using the specified
/// key and value.
/// </summary>
/// <param name="key">The HTTP header key.</param>
/// <param name="value">The HTTP header value.</param>
/// <exception cref="ArgumentNullException">
/// If <paramref name="key"/> is <c>null</c>.
/// <para>-or-</para>
/// <para>If <paramref name="value"/> is <c>null</c>.</para>
/// </exception>
/// <exception cref="ArgumentException">If <paramref name="key"/> is empty.</exception>
public HttpHeader(string key, string value)
{
if (key == null)
throw new ArgumentNullException("key");
if (value == null)
throw new ArgumentNullException("value");
if (string.IsNullOrEmpty(key))
throw new ArgumentException("key cannot be empty");
_key = key;
_value = value;
}
}
}

View File

@ -0,0 +1,68 @@
namespace JSIStudios.SimpleRESTServices.Client
{
/// <summary>
/// Represents the types of HTTP protocol methods that can be used with a REST request.
/// </summary>
public enum HttpMethod
{
/// <summary>
/// Represents an HTTP GET protocol method.
/// </summary>
GET,
/// <summary>
/// Represents an HTTP POST protocol method that is used to post a new entity
/// as an addition to a URI.
/// </summary>
POST,
/// <summary>
/// Represents an HTTP PUT protocol method that is used to replace an entity
/// identified by a URI.
/// </summary>
PUT,
/// <summary>
/// Represents an HTTP DELETE protocol method.
/// </summary>
DELETE,
/// <summary>
/// Represents an HTTP HEAD protocol method. The <see cref="HEAD"/> method is identical to
/// <see cref="GET"/> except that the server only returns message-headers in the response,
/// without a message-body.
/// </summary>
HEAD,
/// <summary>
/// Represents an HTTP PATCH protocol method.
/// </summary>
PATCH,
/// <summary>
/// Represents an HTTP OPTIONS protocol method.
/// </summary>
OPTIONS,
/// <summary>
/// Represents an HTTP TRACE protocol method.
/// </summary>
TRACE,
/// <summary>
/// Represents the HTTP CONNECT protocol method that is used with a proxy that
/// can dynamically switch to tunneling, as in the case of SSL tunneling.
/// </summary>
CONNECT,
/// <summary>
/// Represents an HTTP COPY protocol method.
/// </summary>
COPY,
/// <summary>
/// Represents an HTTP MOVE protocol method.
/// </summary>
MOVE,
}
}

View File

@ -0,0 +1,31 @@
using JSIStudios.SimpleRESTServices.Client.Json;
namespace JSIStudios.SimpleRESTServices.Client
{
/// <summary>
/// This interface provides simple support for serializing and deserializing generic objects to and from strings.
/// </summary>
public interface IStringSerializer
{
/// <summary>
/// Deserializes an object from a string.
/// </summary>
/// <typeparam name="T">The type of the object to deserialize.</typeparam>
/// <param name="content">The serialized representation of the object.</param>
/// <returns>An instance of <typeparamref name="T"/> which <paramref name="content"/> describes.</returns>
/// <exception cref="StringSerializationException">If <paramref name="content"/> could not be deserialized to an object of type <typeparamref name="T"/>.</exception>
T Deserialize<T>(string content);
/// <summary>
/// Serializes an object to a string.
/// </summary>
/// <remarks>
/// The value returned by this method is suitable for deserialization using <see cref="Deserialize{T}"/>.
/// </remarks>
/// <typeparam name="T">The type of the object to serialize.</typeparam>
/// <param name="obj">The object to serialize</param>
/// <returns>A serialized string representation of <paramref name="obj"/>.</returns>
/// <exception cref="StringSerializationException">If <paramref name="obj"/> could not be serialized to a string.</exception>
string Serialize<T>(T obj);
}
}

View File

@ -0,0 +1,27 @@
namespace JSIStudios.SimpleRESTServices.Client.Json
{
/// <summary>
/// Extends <see cref="RequestSettings"/> by setting the default <see cref="RequestSettings.ContentType"/>
/// and <see cref="RequestSettings.Accept"/> values to <c>application/json</c>.
/// </summary>
public class JsonRequestSettings : RequestSettings
{
/// <summary>
/// The content type (MIME type) for a JSON request or response.
/// </summary>
/// <remarks>
/// This value is equal to <c>application/json</c>.
/// </remarks>
public static readonly string JsonContentType = "application/json";
/// <summary>
/// Initializes a new instance of the <see cref="JsonRequestSettings"/> class with the default value
/// <c>application/json</c> for <see cref="RequestSettings.ContentType"/> and <see cref="RequestSettings.Accept"/>.
/// </summary>
public JsonRequestSettings()
{
ContentType = JsonContentType;
Accept = JsonContentType;
}
}
}

View File

@ -0,0 +1,59 @@
using System;
using System.Net;
using JSIStudios.SimpleRESTServices.Core;
namespace JSIStudios.SimpleRESTServices.Client.Json
{
/// <summary>
/// An implementation of <see cref="IRestService"/> that uses JSON notation for
/// the serialized representation of objects.
/// </summary>
public class JsonRestServices : RestServiceBase, IRestService
{
/// <summary>
/// Initializes a new instance of the <see cref="JsonRestServices"/> class with the default
/// JSON string serializer, retry logic, and URL builder.
/// </summary>
public JsonRestServices():this(null) {}
/// <summary>
/// Initializes a new instance of the <see cref="JsonRestServices"/> class with the specified
/// request logger and the default JSON string serializer and URL builder.
/// </summary>
/// <param name="requestLogger">The logger to use for requests. Specify <c>null</c> if requests do not need to be logged.</param>
public JsonRestServices(IRequestLogger requestLogger) : this(requestLogger, new RequestRetryLogic(), new UrlBuilder(), new JsonStringSerializer()) {}
/// <summary>
/// Initializes a new instance of the <see cref="JsonRestServices"/> class with the specified
/// logger, retry logic, URI builder, and string serializer.
/// </summary>
/// <param name="logger">The logger to use for requests. Specify <c>null</c> if requests do not need to be logged.</param>
/// <param name="retryLogic">The retry logic to use for REST operations.</param>
/// <param name="urlBuilder">The URL builder to use for constructing URLs with query parameters.</param>
/// <param name="stringSerializer">The string serializer to use for requests from this service.</param>
/// <exception cref="ArgumentNullException">
/// If <paramref name="retryLogic"/> is <c>null</c>.
/// <para>-or-</para>
/// <para>If <paramref name="urlBuilder"/> is <c>null</c>.</para>
/// <para>-or-</para>
/// <para>If <paramref name="stringSerializer"/> is <c>null</c>.</para>
/// </exception>
public JsonRestServices(IRequestLogger logger, IRetryLogic<Response, HttpStatusCode> retryLogic, IUrlBuilder urlBuilder, IStringSerializer stringSerializer) : base(stringSerializer, logger, retryLogic, urlBuilder) { }
/// <summary>
/// Gets the default <see cref="RequestSettings"/> to use for requests sent from this service.
/// </summary>
/// <remarks>
/// This implementation uses a new instance of <see cref="JsonRequestSettings"/> as the
/// default request settings.
/// </remarks>
protected override RequestSettings DefaultRequestSettings
{
get
{
return new JsonRequestSettings();
}
}
}
}

View File

@ -0,0 +1,86 @@
using System;
using Newtonsoft.Json;
namespace JSIStudios.SimpleRESTServices.Client.Json
{
/// <summary>
/// Provides a default implementation of <see cref="IStringSerializer"/> using JSON for
/// the underlying serialized notation.
/// </summary>
public class JsonStringSerializer : IStringSerializer
{
/// <inheritdoc />
public T Deserialize<T>(string content)
{
if (string.IsNullOrEmpty(content))
return default(T);
try
{
return JsonConvert.DeserializeObject<T>(content,
new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Ignore
});
}
catch (JsonReaderException ex)
{
throw new StringSerializationException(ex);
}
catch (JsonSerializationException ex)
{
throw new StringSerializationException(ex);
}
}
/// <inheritdoc />
public string Serialize<T>(T obj)
{
if (Equals(obj, default(T)))
return null;
try
{
return JsonConvert.SerializeObject(obj,
new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Ignore
});
}
catch (JsonReaderException ex)
{
throw new StringSerializationException(ex);
}
catch (JsonSerializationException ex)
{
throw new StringSerializationException(ex);
}
}
}
/// <summary>
/// The exception thrown when string serialization or deserialization fails.
/// </summary>
public class StringSerializationException : Exception
{
/// <summary>
/// Initializes a new instance of the <see cref="StringSerializationException"/> class
/// with a reference to the inner exception that is the cause of this exception.
/// </summary>
/// <param name="innerException">The exception that is the cause of the current exception.
/// If the <paramref name="innerException"/> parameter is not a null reference, the current
/// exception is raised in a <b>catch</b> block that handles the inner exception.</param>
public StringSerializationException(Exception innerException)
: base(GetMessageFromInnerException(innerException, "An error occurred during string serialization."), innerException)
{
}
private static string GetMessageFromInnerException(Exception innerException, string defaultMessage)
{
if (innerException == null)
return defaultMessage;
return innerException.Message ?? defaultMessage;
}
}
}

View File

@ -0,0 +1,186 @@
using System;
using System.Collections.Generic;
using System.Net;
using JSIStudios.SimpleRESTServices.Client.Json;
using JSIStudios.SimpleRESTServices.Core;
namespace JSIStudios.SimpleRESTServices.Client
{
/// <summary>
/// Specifies the settings for an HTTP REST request.
/// </summary>
/// <remarks>
/// The base implementation does not specify values for the <see cref="RequestSettings.ContentType"/>
/// or <see cref="RequestSettings.Accept"/> properties. In most cases, these should be properly
/// set for the particular application either manually or in the constructor of a derived class
/// (e.g. <see cref="JsonRequestSettings"/>).
/// </remarks>
public class RequestSettings
{
/// <summary>
/// Gets or sets the value of the Content-Type HTTP header. If this value is
/// <c>null</c>, the Content-Type header is omitted from the request.
/// </summary>
/// <remarks>
/// In the base implementation, the default value is <c>null</c>.
/// </remarks>
public virtual string ContentType { get; set; }
/// <summary>
/// Gets or sets the number of times this request should be retried if it fails.
/// </summary>
/// <remarks>
/// In the base implementation, the default value is 0.
/// </remarks>
public int RetryCount { get; set; }
/// <summary>
/// Gets or sets the delay before retrying a failed request.
/// </summary>
/// <remarks>
/// In the base implementation, the default value is <see cref="TimeSpan.Zero"/>.
/// </remarks>
public TimeSpan RetryDelay { get; set; }
/// <summary>
/// Gets or sets the set of HTTP status codes greater than or equal to 300 which
/// should be considered a successful result for this request. A value of
/// <c>null</c> is allowed, and should be treated as an empty collection.
/// </summary>
/// <remarks>
/// In the base implementation, the default value is <c>null</c>.
/// </remarks>
public IEnumerable<HttpStatusCode> Non200SuccessCodes { get; set; }
/// <summary>
/// Gets or sets the HTTP Accept header, which specifies the MIME types that are
/// acceptable for the response. If this value is <c>null</c>, the Accept header
/// is omitted from the request.
/// </summary>
/// <remarks>
/// In the base implementation, the default value is <c>null</c>.
/// </remarks>
public virtual string Accept { get; set; }
/// <summary>
/// Gets or sets a map of user-defined actions to execute in response to specific
/// HTTP status codes. A value of <c>null</c> is allowed, and should be treated as
/// an empty collection.
/// </summary>
/// <remarks>
/// In the base implementation, the default value is <c>null</c>.
///
/// <para>If this value is non-null, and the request results in a status code not
/// present in this collection, no custom action is executed for the response.</para>
/// </remarks>
public Dictionary<HttpStatusCode, Action<Response>> ResponseActions { get; set; }
/// <summary>
/// Gets or sets the value of the User-Agent HTTP header. If this value is <c>null</c>,
/// the User-Agent header is omitted from the request.
/// </summary>
/// <remarks>
/// In the base implementation, the default value is <c>null</c>.
/// </remarks>
public string UserAgent { get; set; }
/// <summary>
/// Gets or sets the credentials to use for this request. This value can be
/// <c>null</c> if credentials are not specified.
/// </summary>
/// <remarks>
/// In the base implementation, the default value is <c>null</c>.
/// </remarks>
public ICredentials Credentials { get; set; }
/// <summary>
/// Gets or sets the request timeout.
/// </summary>
/// <value>
/// The time to wait before the request times out. In the base
/// implementation, the default value is 100,000 milliseconds (100 seconds).
/// </value>
public TimeSpan Timeout { get; set; }
/// <summary>
/// Gets or sets a value indicating whether to send data in segments to the
/// Internet resource.
/// </summary>
/// <value>
/// <c>true</c> to send data to the Internet resource in segments; otherwise,
/// <c>false</c>. In the base implementation, the default value is <c>false</c>.
/// </value>
public bool ChunkRequest { get; set; }
/// <summary>
/// Gets or sets a user-defined collection to pass as the final argument to the
/// <see cref="IRequestLogger.Log"/> callback method.
/// </summary>
/// <remarks>
/// This value not used directly within SimpleRESTServices.
/// </remarks>
public Dictionary<string, string> ExtendedLoggingData { get; set; }
/// <summary>
/// Gets or sets the value of the Content-Length HTTP header.
/// </summary>
/// <remarks>
/// When this value is 0, the <see cref="AllowZeroContentLength"/> property controls
/// whether or not the Content-Length header is included with the request.
///
/// <para>In the base implementation, the default value is 0.</para>
/// </remarks>
public long ContentLength { get; set; }
/// <summary>
/// Gets or sets a value indicating whether or not 0 is a valid value for the Content-Length HTTP header
/// for this request.
/// </summary>
/// <remarks>
/// When <see cref="ContentLength"/> is non-zero, this value is ignored.
/// <para>When <see cref="ContentLength"/> is zero and this is <c>true</c>, the
/// Content-Length HTTP header is added to the request with an explicit value of 0.
/// Otherwise, when <see cref="ContentLength"/> is 0 the Content-Length HTTP header
/// is omitted from the request.</para>
///
/// <para>In the base implementation, the default value is <c>false</c>.</para>
/// </remarks>
public bool AllowZeroContentLength { get; set; }
/// <summary>
/// Gets or sets the maximum number of connections allowed on the <see cref="ServicePoint"/> object
/// used for the request. If the value is <c>null</c>, the connection limit value for the
/// <see cref="ServicePoint"/> object is not altered.
/// </summary>
public int? ConnectionLimit
{
get;
set;
}
/// <summary>
/// Gets or sets a value indicating whether the request should follow redirection responses.
/// </summary>
/// <remarks>
/// <para>The default value is <see langword="true"/>.</para>
/// </remarks>
/// <value>
/// <see langword="true"/> if the request should follow redirection responses; otherwise,
/// <see langword="false"/>.
/// </value>
/// <seealso cref="HttpWebRequest.AllowAutoRedirect"/>
public bool AllowAutoRedirect { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="RequestSettings"/> class with the default values.
/// </summary>
public RequestSettings()
{
RetryCount = 0;
RetryDelay = TimeSpan.Zero;
Non200SuccessCodes = null;
Timeout = TimeSpan.FromMilliseconds(100000);
AllowAutoRedirect = true;
}
}
}

View File

@ -0,0 +1,70 @@
using System;
using System.Collections.Generic;
using System.Net;
namespace JSIStudios.SimpleRESTServices.Client
{
/// <summary>
/// Represents the basic response of an HTTP REST request, where the body of the response
/// is stored as a text string.
/// </summary>
[Serializable]
public class Response
{
/// <summary>
/// Gets the HTTP status code for this response.
/// </summary>
public HttpStatusCode StatusCode { get; private set; }
/// <summary>
/// Gets a string representation of the HTTP status code for this response.
/// </summary>
public string Status { get; private set; }
/// <summary>
/// Gets a collection of all HTTP headers included with this response.
/// </summary>
public IList<HttpHeader> Headers { get; private set; }
/// <summary>
/// Gets the raw body of this HTTP response as a text string.
/// </summary>
public string RawBody { get; private set; }
/// <summary>
/// Initializes a new instance of the <see cref="Response"/> class with the given HTTP status code,
/// status, headers, and raw body.
/// </summary>
/// <param name="responseCode">The HTTP status code.</param>
/// <param name="status">A string representation of the HTTP status code.</param>
/// <param name="headers">A collection of all HTTP headers included with this response.</param>
/// <param name="rawBody">
/// The raw body of this HTTP response as a text string. When included in the response, this
/// value should be loaded with the encoding specified in the Content-Encoding and/or
/// Content-Type HTTP headers.
/// </param>
public Response(HttpStatusCode responseCode, string status, IList<HttpHeader> headers, string rawBody)
{
StatusCode = responseCode;
Status = status;
Headers = headers;
RawBody = rawBody;
}
/// <summary>
/// Initializes a new instance of the <see cref="Response"/> class with the given HTTP status code,
/// headers, and raw body.
/// </summary>
/// <param name="statusCode">The HTTP status code.</param>
/// <param name="headers">A collection of all HTTP headers included with this response.</param>
/// <param name="rawBody">
/// The raw body of this HTTP response as a text string. When included in the response, this
/// value should be loaded with the encoding specified in the Content-Encoding and/or
/// Content-Type HTTP headers.
/// </param>
public Response(HttpStatusCode statusCode, IList<HttpHeader> headers, string rawBody)
: this(statusCode, statusCode.ToString(), headers, rawBody)
{
}
}
}

View File

@ -0,0 +1,68 @@
using System;
using System.Collections.Generic;
using System.Net;
namespace JSIStudios.SimpleRESTServices.Client
{
/// <summary>
/// Extends <see cref="Response"/> to include a strongly-typed return value
/// from the response.
/// </summary>
/// <typeparam name="T">The type of the data included with the response.</typeparam>
[Serializable]
public class Response<T> : Response
{
/// <summary>
/// Gets the strongly-typed representation of the value included with this response.
/// </summary>
public T Data { get; private set; }
/// <summary>
/// Initializes a new instance of the <see cref="Response{T}"/> class with the given HTTP status code,
/// status, strongly-type data, headers, and raw body.
/// </summary>
/// <param name="responseCode">The HTTP status code.</param>
/// <param name="status">A string representation of the HTTP status code.</param>
/// <param name="data">The strongly-typed data representation of the value returned with this response.</param>
/// <param name="headers">A collection of all HTTP headers included with this response.</param>
/// <param name="rawBody">
/// The raw body of this HTTP response as a text string. When included in the response, this
/// value should be loaded with the encoding specified in the Content-Encoding and/or
/// Content-Type HTTP headers.
/// </param>
public Response(HttpStatusCode responseCode, string status, T data, IList<HttpHeader> headers, string rawBody)
: base(responseCode, status, headers, rawBody)
{
Data = data;
}
/// <summary>
/// Initializes a new instance of the <see cref="Response{T}"/> class with the given HTTP status code,
/// strongly-type data, headers, and raw body.
/// </summary>
/// <param name="statusCode">The HTTP status code.</param>
/// <param name="data">The strongly-typed data representation of the value returned with this response.</param>
/// <param name="headers">A collection of all HTTP headers included with this response.</param>
/// <param name="rawBody">
/// The raw body of this HTTP response as a text string. When included in the response, this
/// value should be loaded with the encoding specified in the Content-Encoding and/or
/// Content-Type HTTP headers.
/// </param>
public Response(HttpStatusCode statusCode, T data, IList<HttpHeader> headers, string rawBody)
: this(statusCode, statusCode.ToString(), data, headers, rawBody)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Response{T}"/> class by adding a strongly-typed
/// data value to a base response.
/// </summary>
/// <param name="baseResponse">The base response.</param>
/// <param name="data">The strongly-typed data representation of the value returned with this response.</param>
public Response(Response baseResponse, T data)
: this((baseResponse == null) ? default(int) : baseResponse.StatusCode,
(baseResponse == null) ? null : baseResponse.Status, data,
(baseResponse == null) ? null : baseResponse.Headers,
(baseResponse == null) ? null : baseResponse.RawBody) { }
}
}

View File

@ -0,0 +1,583 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using JSIStudios.SimpleRESTServices.Client.Json;
using JSIStudios.SimpleRESTServices.Core;
#if !NET35
using System.Diagnostics.Contracts;
#endif
namespace JSIStudios.SimpleRESTServices.Client
{
/// <summary>
/// Implements basic support for <see cref="IRestService"/> in terms of an implementation
/// of <see cref="IRetryLogic{T, T2}"/>, <see cref="IRequestLogger"/>,
/// <see cref="IUrlBuilder"/>, and <see cref="IStringSerializer"/>.
/// </summary>
public abstract class RestServiceBase : IRestService
{
private readonly IRetryLogic<Response, HttpStatusCode> _retryLogic;
private readonly IRequestLogger _logger;
private readonly IUrlBuilder _urlBuilder;
private readonly IStringSerializer _stringSerializer;
/// <summary>
/// Initializes a new instance of the <see cref="RestServiceBase"/> class with the specified string serializer
/// and the default retry logic and URL builder.
/// </summary>
/// <param name="stringSerializer">The string serializer to use for requests from this service.</param>
/// <exception cref="ArgumentNullException">If <paramref name="stringSerializer"/> is <c>null</c>.</exception>
protected RestServiceBase(IStringSerializer stringSerializer) : this(stringSerializer, null) { }
/// <summary>
/// Initializes a new instance of the <see cref="RestServiceBase"/> class with the specified string serializer
/// and logger, and the default retry logic and URL builder.
/// </summary>
/// <param name="stringSerializer">The string serializer to use for requests from this service.</param>
/// <param name="requestLogger">The logger to use for requests. Specify <c>null</c> if requests do not need to be logged.</param>
/// <exception cref="ArgumentNullException">If <paramref name="stringSerializer"/> is <c>null</c>.</exception>
protected RestServiceBase(IStringSerializer stringSerializer, IRequestLogger requestLogger) : this(stringSerializer, requestLogger, new RequestRetryLogic(), new UrlBuilder()) { }
/// <summary>
/// Initializes a new instance of the <see cref="RestServiceBase"/> class with the specified string serializer,
/// logger, retry logic, and URI builder.
/// </summary>
/// <param name="stringSerializer">The string serializer to use for requests from this service.</param>
/// <param name="logger">The logger to use for requests. Specify <c>null</c> if requests do not need to be logged.</param>
/// <param name="retryLogic">The retry logic to use for REST operations.</param>
/// <param name="urlBuilder">The URL builder to use for constructing URLs with query parameters.</param>
/// <exception cref="ArgumentNullException">
/// If <paramref name="stringSerializer"/> is <c>null</c>.
/// <para>-or-</para>
/// <para>If <paramref name="retryLogic"/> is <c>null</c>.</para>
/// <para>-or-</para>
/// <para>If <paramref name="urlBuilder"/> is <c>null</c>.</para>
/// </exception>
protected RestServiceBase(IStringSerializer stringSerializer, IRequestLogger logger, IRetryLogic<Response, HttpStatusCode> retryLogic, IUrlBuilder urlBuilder)
{
if (stringSerializer == null)
throw new ArgumentNullException("stringSerializer");
if (retryLogic == null)
throw new ArgumentNullException("retryLogic");
if (urlBuilder == null)
throw new ArgumentNullException("urlBuilder");
_retryLogic = retryLogic;
_logger = logger;
_urlBuilder = urlBuilder;
_stringSerializer = stringSerializer;
}
/// <summary>
/// Gets the default <see cref="RequestSettings"/> to use for requests sent from this service.
/// </summary>
protected virtual RequestSettings DefaultRequestSettings
{
get
{
return new RequestSettings();
}
}
/// <inheritdoc/>
public virtual Response<T> Execute<T, TBody>(string url, HttpMethod method, TBody body, Dictionary<string, string> headers, Dictionary<string, string> queryStringParameters, RequestSettings settings)
{
if (url == null)
throw new ArgumentNullException("url");
if (string.IsNullOrEmpty(url))
throw new ArgumentException("url cannot be empty");
return Execute<T, TBody>(new Uri(url), method, body, headers, queryStringParameters, settings);
}
/// <inheritdoc/>
public virtual Response<T> Execute<T, TBody>(Uri url, HttpMethod method, TBody body, Dictionary<string, string> headers, Dictionary<string, string> queryStringParameters, RequestSettings settings)
{
if (url == null)
throw new ArgumentNullException("url");
var rawBody = _stringSerializer.Serialize(body);
return Execute<T>(url, method, rawBody, headers, queryStringParameters, settings);
}
/// <inheritdoc/>
public virtual Response<T> Execute<T>(string url, HttpMethod method, string body, Dictionary<string, string> headers, Dictionary<string, string> queryStringParameters, RequestSettings settings)
{
if (url == null)
throw new ArgumentNullException("url");
if (string.IsNullOrEmpty(url))
throw new ArgumentException("url cannot be empty");
return Execute<T>(new Uri(url), method, body, headers, queryStringParameters, settings);
}
/// <inheritdoc/>
public virtual Response<T> Execute<T>(Uri url, HttpMethod method, string body, Dictionary<string, string> headers, Dictionary<string, string> queryStringParameters, RequestSettings settings)
{
if (url == null)
throw new ArgumentNullException("url");
return Execute(url, method, BuildWebResponse<T>, body, headers, queryStringParameters, settings) as Response<T>;
}
/// <inheritdoc/>
public virtual Response Execute<TBody>(string url, HttpMethod method, TBody body, Dictionary<string, string> headers, Dictionary<string, string> queryStringParameters, RequestSettings settings)
{
if (url == null)
throw new ArgumentNullException("url");
if (string.IsNullOrEmpty(url))
throw new ArgumentException("url cannot be empty");
return Execute(new Uri(url), method, body, headers, queryStringParameters, settings);
}
/// <inheritdoc/>
public virtual Response Execute<TBody>(Uri url, HttpMethod method, TBody body, Dictionary<string, string> headers, Dictionary<string, string> queryStringParameters, RequestSettings settings)
{
if (url == null)
throw new ArgumentNullException("url");
var rawBody = _stringSerializer.Serialize(body);
return Execute(url, method, rawBody, headers, queryStringParameters, settings);
}
/// <inheritdoc/>
public virtual Response Execute(string url, HttpMethod method, string body, Dictionary<string, string> headers, Dictionary<string, string> queryStringParameters, RequestSettings settings)
{
if (url == null)
throw new ArgumentNullException("url");
if (string.IsNullOrEmpty(url))
throw new ArgumentException("url cannot be empty");
return Execute(new Uri(url), method, body, headers, queryStringParameters, settings);
}
/// <inheritdoc/>
public virtual Response Execute(Uri url, HttpMethod method, string body, Dictionary<string, string> headers, Dictionary<string, string> queryStringParameters, RequestSettings settings)
{
if (url == null)
throw new ArgumentNullException("url");
return Execute(url, method, null, body, headers, queryStringParameters, settings);
}
/// <inheritdoc/>
public virtual Response Execute(Uri url, HttpMethod method, Func<HttpWebResponse, bool, Response> responseBuilderCallback, string body, Dictionary<string, string> headers, Dictionary<string, string> queryStringParameters, RequestSettings settings)
{
if (url == null)
throw new ArgumentNullException("url");
return ExecuteRequest(url, method, responseBuilderCallback, headers, queryStringParameters, settings, (req) =>
{
// Encode the parameters as form data:
if (!string.IsNullOrEmpty(body))
{
byte[] formData = UTF8Encoding.UTF8.GetBytes(body);
req.ContentLength = formData.Length;
// Send the request:
using (Stream post = req.GetRequestStream())
{
post.Write(formData, 0, formData.Length);
}
}
return body;
});
}
/// <inheritdoc/>
public virtual Response<T> Stream<T>(string url, HttpMethod method, Stream content, int bufferSize, long maxReadLength, Dictionary<string, string> headers, Dictionary<string, string> queryStringParameters, RequestSettings settings, Action<long> progressUpdated)
{
if (url == null)
throw new ArgumentNullException("url");
if (content == null)
throw new ArgumentNullException("content");
if (string.IsNullOrEmpty(url))
throw new ArgumentException("url cannot be empty");
if (bufferSize <= 0)
throw new ArgumentOutOfRangeException("bufferSize");
if (maxReadLength < 0)
throw new ArgumentOutOfRangeException("maxReadLength");
return Stream<T>(new Uri(url), method, content, bufferSize, maxReadLength, headers, queryStringParameters, settings, progressUpdated) as Response<T>;
}
/// <inheritdoc/>
public virtual Response Stream(string url, HttpMethod method, Stream content, int bufferSize, long maxReadLength, Dictionary<string, string> headers, Dictionary<string, string> queryStringParameters, RequestSettings settings, Action<long> progressUpdated)
{
if (url == null)
throw new ArgumentNullException("url");
if (content == null)
throw new ArgumentNullException("content");
if (string.IsNullOrEmpty(url))
throw new ArgumentException("url cannot be empty");
if (bufferSize <= 0)
throw new ArgumentOutOfRangeException("bufferSize");
if (maxReadLength < 0)
throw new ArgumentOutOfRangeException("maxReadLength");
return Stream(new Uri(url), method, content, bufferSize, maxReadLength, headers, queryStringParameters, settings, progressUpdated);
}
/// <inheritdoc/>
public virtual Response<T> Stream<T>(Uri url, HttpMethod method, Stream content, int bufferSize, long maxReadLength, Dictionary<string, string> headers, Dictionary<string, string> queryStringParameters, RequestSettings settings, Action<long> progressUpdated)
{
if (url == null)
throw new ArgumentNullException("url");
if (content == null)
throw new ArgumentNullException("content");
if (bufferSize <= 0)
throw new ArgumentOutOfRangeException("bufferSize");
if (maxReadLength < 0)
throw new ArgumentOutOfRangeException("maxReadLength");
return Stream(url, method, BuildWebResponse<T>, content, bufferSize, maxReadLength, headers, queryStringParameters, settings, progressUpdated) as Response<T>;
}
/// <inheritdoc/>
public virtual Response Stream(Uri url, HttpMethod method, Stream content, int bufferSize, long maxReadLength, Dictionary<string, string> headers, Dictionary<string, string> queryStringParameters, RequestSettings settings, Action<long> progressUpdated)
{
if (url == null)
throw new ArgumentNullException("url");
if (content == null)
throw new ArgumentNullException("content");
if (bufferSize <= 0)
throw new ArgumentOutOfRangeException("bufferSize");
if (maxReadLength < 0)
throw new ArgumentOutOfRangeException("maxReadLength");
return Stream(url, method, null, content, bufferSize, maxReadLength, headers, queryStringParameters, settings, progressUpdated);
}
/// <summary>
/// Executes a REST request with a <see cref="System.IO.Stream"/> <paramref name="content"/>
/// and user-defined callback function for constructing the resulting <see cref="Response"/>
/// object.
/// </summary>
/// <param name="url">The base URI.</param>
/// <param name="method">The HTTP method to use for the request.</param>
/// <param name="responseBuilderCallback">A user-specified function used to construct the resulting <see cref="Response"/>
/// object from the <see cref="HttpWebResponse"/> and a Boolean value specifying whether or not a <see cref="WebException"/>
/// was thrown during the request. If this value is <c>null</c>, this method is equivalent to calling
/// <see cref="Stream(Uri, HttpMethod, Stream, int, long, Dictionary{string, string}, Dictionary{string, string}, RequestSettings, Action{long})"/>.</param>
/// <param name="content">A stream providing the body of the request.</param>
/// <param name="bufferSize">
/// The size of the buffer used for copying data from <paramref name="content"/> to the
/// HTTP request stream.
/// </param>
/// <param name="maxReadLength">
/// The maximum number of bytes to send with the request. This parameter is optional.
/// If the value is 0, the request will include all data from <paramref name="content"/>.
/// </param>
/// <param name="headers">
/// A collection of custom HTTP headers to include with the request. This parameter is
/// optional. If the value is <c>null</c>, no custom headers are added to the HTTP request.
/// </param>
/// <param name="queryStringParameters">
/// A collection of parameters to add to the query string portion of the request URI.
/// This parameter is optional. If the value is <c>null</c>, no parameters are added
/// to the query string.
/// </param>
/// <param name="settings">
/// The settings to use for the request. This parameters is optional. If the value is
/// <c>null</c>, an implementation-specific set of default settings will be used for the request.
/// </param>
/// <param name="progressUpdated">
/// A user-defined callback function for reporting progress of the send operation.
/// This parameter is optional. If the value is <c>null</c>, the method does not report
/// progress updates to the caller.
/// </param>
/// <returns>Returns a <see cref="Response"/> object containing the HTTP status code, headers,
/// and body from the REST response.</returns>
/// <exception cref="ArgumentNullException">
/// If <paramref name="url"/> is <c>null</c>.
/// <para>-or-</para>
/// <para>If <paramref name="content"/> is <c>null</c>.</para>
/// </exception>
/// <exception cref="ArgumentOutOfRangeException">
/// If <paramref name="bufferSize"/> is less than or equal to zero.
/// <para>-or-</para>
/// <para>If <paramref name="maxReadLength"/> is less than zero.</para>
/// </exception>
/// <exception cref="NotSupportedException">If <paramref name="method"/> is not supported by the service.</exception>
public virtual Response Stream(Uri url, HttpMethod method, Func<HttpWebResponse, bool, Response> responseBuilderCallback, Stream content, int bufferSize, long maxReadLength, Dictionary<string, string> headers, Dictionary<string, string> queryStringParameters, RequestSettings settings, Action<long> progressUpdated)
{
if (url == null)
throw new ArgumentNullException("url");
if (content == null)
throw new ArgumentNullException("content");
if (bufferSize <= 0)
throw new ArgumentOutOfRangeException("bufferSize");
if (maxReadLength < 0)
throw new ArgumentOutOfRangeException("maxReadLength");
return ExecuteRequest(url, method, responseBuilderCallback, headers, queryStringParameters, settings, (req) =>
{
long bytesWritten = 0;
if (settings.ChunkRequest || maxReadLength > 0 )
{
req.SendChunked = settings.ChunkRequest;
req.AllowWriteStreamBuffering = false;
req.ContentLength = content.Length > maxReadLength ? maxReadLength : content.Length;
}
using (Stream stream = req.GetRequestStream())
{
var buffer = new byte[bufferSize];
int count;
while ((count = content.Read(buffer, 0, maxReadLength > 0 ? (int)Math.Min(bufferSize, maxReadLength - bytesWritten) : bufferSize)) > 0)
{
bytesWritten += count;
stream.Write(buffer, 0, count);
if (progressUpdated != null)
progressUpdated(bytesWritten);
if (maxReadLength > 0 && bytesWritten >= maxReadLength)
break;
}
}
return "[STREAM CONTENT]";
});
}
/// <summary>
/// Executes a REST request indirectly via a callback function <paramref name="executeCallback"/>,
/// and using a user-defined callback function <paramref name="responseBuilderCallback"/> for
/// constructing the resulting <see cref="Response"/> object.
/// </summary>
/// <remarks>
/// The callback method <paramref name="executeCallback"/> is responsible for setting the body
/// of the request, if any, before executing the request. The callback method returns a string
/// representation of the body of the final request when available, otherwise returns a string
/// indicating the body is no longer available (e.g. was sent as a stream, or is binary). The
/// result is only required for passing as an argument to <see cref="IRequestLogger.Log"/>.
///
/// <para>The Boolean argument to <paramref name="responseBuilderCallback"/> indicates whether
/// or not an exception was thrown while executing the request. The value is <c>true</c>
/// if an exception occurred, otherwise <c>false</c>.</para>
/// </remarks>
/// <param name="url">The base URI.</param>
/// <param name="method">The HTTP method to use for the request.</param>
/// <param name="responseBuilderCallback">A user-specified function used to construct the resulting <see cref="Response"/>
/// object from the <see cref="HttpWebResponse"/> and a Boolean value specifying whether or not a <see cref="WebException"/>
/// was thrown during the request. If this value is <c>null</c>, a default method is used to construct
/// the resulting <see cref="Response"/> object.</param>
/// <param name="headers">
/// A collection of custom HTTP headers to include with the request. If the value is
/// <c>null</c>, no custom headers are added to the HTTP request.
/// </param>
/// <param name="queryStringParameters">
/// A collection of parameters to add to the query string portion of the request URI.
/// If the value is <c>null</c>, no parameters are added to the query string.
/// </param>
/// <param name="settings">
/// The settings to use for the request. If the value is <c>null</c>, the default settings returned
/// by <see cref="DefaultRequestSettings"/> will be used for the request.
/// </param>
/// <param name="executeCallback"></param>
/// <returns>Returns a <see cref="Response"/> object containing the HTTP status code, headers,
/// and body from the REST response.</returns>
/// <exception cref="ArgumentNullException">
/// If <paramref name="url"/> is <c>null</c>.
/// <para>-or-</para>
/// <para>If <paramref name="executeCallback"/> is <c>null</c>.</para>
/// </exception>
/// <exception cref="NotSupportedException">If <paramref name="method"/> is not supported by the service.</exception>
public virtual Response ExecuteRequest(Uri url, HttpMethod method, Func<HttpWebResponse, bool, Response> responseBuilderCallback, Dictionary<string, string> headers, Dictionary<string, string> queryStringParameters, RequestSettings settings, Func<HttpWebRequest, string> executeCallback)
{
if (url == null)
throw new ArgumentNullException("url");
if (executeCallback == null)
throw new ArgumentNullException("executeCallback");
url = _urlBuilder.Build(url, queryStringParameters);
if (settings == null)
settings = DefaultRequestSettings;
return _retryLogic.Execute(() =>
{
Response response;
var startTime = DateTimeOffset.UtcNow;
string requestBodyText = null;
try
{
var req = WebRequest.Create(url) as HttpWebRequest;
req.Method = method.ToString();
req.ContentType = settings.ContentType;
req.Accept = settings.Accept;
req.AllowAutoRedirect = settings.AllowAutoRedirect;
if(settings.ContentLength > 0 || settings.AllowZeroContentLength)
req.ContentLength = settings.ContentLength;
if (settings.ConnectionLimit != null)
req.ServicePoint.ConnectionLimit = settings.ConnectionLimit.Value;
req.Timeout = (int)settings.Timeout.TotalMilliseconds;
if (!string.IsNullOrEmpty(settings.UserAgent))
req.UserAgent = settings.UserAgent;
if (settings.Credentials != null)
req.Credentials = settings.Credentials;
if (headers != null)
{
foreach (var header in headers)
{
req.Headers.Add(header.Key, header.Value);
}
}
requestBodyText = executeCallback(req);
using (var resp = req.GetResponse() as HttpWebResponse)
{
if (responseBuilderCallback != null)
response = responseBuilderCallback(resp, false);
else
response = BuildWebResponse(resp);
}
}
catch (WebException ex)
{
if (ex.Response == null)
throw;
using (var resp = ex.Response as HttpWebResponse)
{
if (responseBuilderCallback != null)
response = responseBuilderCallback(resp, true);
else
response = BuildWebResponse(resp);
}
}
var endTime = DateTimeOffset.UtcNow;
// Log the request
if (_logger != null)
_logger.Log(method, url.OriginalString, headers, requestBodyText, response, startTime, endTime, settings.ExtendedLoggingData);
if (response != null && settings.ResponseActions != null && settings.ResponseActions.ContainsKey(response.StatusCode))
{
var action = settings.ResponseActions[response.StatusCode];
if (action != null)
action(response);
}
return response;
}, settings.Non200SuccessCodes, settings.RetryCount, settings.RetryDelay);
}
/// <summary>
/// Build a <see cref="Response"/> for a given <see cref="HttpWebResponse"/>.
/// </summary>
/// <param name="resp">The response from the REST request.</param>
/// <returns>A <see cref="Response"/> object representing the result of the REST API call.</returns>
/// <exception cref="ArgumentNullException">If <paramref name="resp"/> is <c>null</c>.</exception>
private Response BuildWebResponse(HttpWebResponse resp)
{
if (resp == null)
throw new ArgumentNullException("resp");
string respBody;
using (var reader = new StreamReader(resp.GetResponseStream(), GetEncoding(resp)))
{
respBody = reader.ReadToEnd();
}
var respHeaders =
resp.Headers.AllKeys.Select(key => new HttpHeader(key, resp.GetResponseHeader(key)))
.ToList();
return new Response(resp.StatusCode, respHeaders, respBody);
}
/// <summary>
/// Determines the <see cref="Encoding"/> to use for reading an <see cref="HttpWebResponse"/>
/// body as text based on the response headers.
/// </summary>
/// <remarks>
/// If the response provides the <c>Content-Encoding</c> header, then it is used.
/// Otherwise, if the optional <c>charset</c> parameter to the <c>Content-Type</c> header
/// is provided, then it is used. If no encoding is specified in the headers, or if the
/// encoding specified in the headers is not valid, <see cref="Encoding.Default"/> is
/// used.
/// </remarks>
/// <param name="response">The response to examine</param>
/// <returns>The <see cref="Encoding"/> to use when reading the response stream as text.</returns>
/// <exception cref="ArgumentNullException"><paramref name="response"/> is <c>null</c>.</exception>
private Encoding GetEncoding(HttpWebResponse response)
{
if (response == null)
throw new ArgumentNullException("response");
#if !NET35
Contract.Ensures(Contract.Result<Encoding>() != null);
Contract.EndContractBlock();
#endif
string contentEncoding = response.ContentEncoding;
if (!string.IsNullOrEmpty(contentEncoding))
{
try
{
return Encoding.GetEncoding(contentEncoding);
}
catch (ArgumentException)
{
// continue below
}
}
string characterSet = response.CharacterSet;
if (string.IsNullOrEmpty(characterSet))
return Encoding.Default;
try
{
return Encoding.GetEncoding(characterSet) ?? Encoding.Default;
}
catch (ArgumentException)
{
return Encoding.Default;
}
}
/// <summary>
/// Builds a <see cref="Response{T}"/> for a given <see cref="HttpWebResponse"/>
/// containing a serialized representation of strongly-typed data in the body of
/// the response.
/// </summary>
/// <typeparam name="T">The object model type for the data contained in the body of <paramref name="resp"/>.</typeparam>
/// <param name="resp">The response from the REST request.</param>
/// <param name="isError">Indicates whether the response is an error response. If the value is <c>true</c> the response
/// will not be deserialized to <typeparamref name="T"/></param>
/// <returns>A <see cref="Response{T}"/> instance representing the response from the REST API call.</returns>
/// <exception cref="ArgumentNullException">If <paramref name="resp"/> is <c>null</c>.</exception>
/// <exception cref="StringSerializationException">
/// If the body of <paramref name="resp"/> could not be deserialized to an object of type <typeparamref name="T"/>.
/// </exception>
private Response<T> BuildWebResponse<T>(HttpWebResponse resp, bool isError = false)
{
var baseReponse = BuildWebResponse(resp);
T data = default(T);
if (!isError)
{
if (baseReponse != null && !string.IsNullOrEmpty(baseReponse.RawBody))
data = _stringSerializer.Deserialize<T>(baseReponse.RawBody);
}
return new Response<T>(baseReponse, data);
}
}
}

View File

@ -0,0 +1,60 @@
using System;
using System.Collections.Generic;
using System.Linq;
using JSIStudios.SimpleRESTServices.Core;
namespace JSIStudios.SimpleRESTServices.Client
{
/// <summary>
/// A simple, default implementation of a URI builder.
/// </summary>
public class UrlBuilder : IUrlBuilder
{
/// <inheritdoc/>
public Uri Build(Uri baseUrl, Dictionary<string, string> queryStringParameters)
{
if (baseUrl == null)
throw new ArgumentNullException("baseUrl");
return new Uri(Build(baseUrl.AbsoluteUri, queryStringParameters));
}
/// <summary>
/// Constructs a complete URI for an HTTP request using a base URI and a
/// collection of query string parameters.
/// </summary>
/// <remarks>
/// If <paramref name="baseAbsoluteUrl"/> already contains a query string, the specified
/// <paramref name="queryStringParameters"/> are appended to the existing query string.
/// This method does not perform substitution for any template parameters
/// which may exist in <paramref name="baseAbsoluteUrl"/>. If <paramref name="queryStringParameters"/>
/// is <c>null</c> or empty, <paramref name="baseAbsoluteUrl"/> is returned unchanged.
/// </remarks>
/// <param name="baseAbsoluteUrl">The base URI.</param>
/// <param name="queryStringParameters">A collection of parameters to place in the URI query string,
/// or <c>null</c> if there are no parameters.</param>
/// <returns>A <see cref="Uri"/> constructed from <paramref name="baseAbsoluteUrl"/> and the specified
/// <paramref name="queryStringParameters"/>.</returns>
/// <exception cref="ArgumentNullException">If <paramref name="baseAbsoluteUrl"/> is <c>null</c>.</exception>
public string Build(string baseAbsoluteUrl, Dictionary<string, string> queryStringParameters)
{
if (baseAbsoluteUrl == null)
throw new ArgumentNullException("baseAbsoluteUrl");
if (queryStringParameters != null && queryStringParameters.Count > 0)
{
var paramsCombinedList =
queryStringParameters.Select(
param =>
string.Format("{0}={1}", System.Web.HttpUtility.UrlEncode(param.Key),
System.Web.HttpUtility.UrlEncode(param.Value)));
var paramsCombined = string.Join("&", paramsCombinedList.ToArray());
var separator = baseAbsoluteUrl.Contains("?") ? "&" : "?";
return baseAbsoluteUrl + separator + paramsCombined;
}
return baseAbsoluteUrl;
}
}
}

View File

@ -0,0 +1,88 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading;
using JSIStudios.SimpleRESTServices.Core;
namespace JSIStudios.SimpleRESTServices.Client
{
/// <summary>
/// Provides a simple default implementation of <see cref="IRetryLogic{T, T2}"/>
/// for HTTP REST requests.
/// </summary>
/// <remarks>
/// This implementation of <see cref="IRetryLogic{T, T2}"/> invokes the callback
/// method until one of the following conditions is met.
///
/// <list type="bullet">
/// <item>The operation has been attempted <c>retryCount</c> times.</item>
/// <item>The <see cref="Response"/> status code is less than 300.</item>
/// <item>The <see cref="Response"/> status code is contained in the
/// <c>non200SuccessCodes</c> collection.</item>
/// </list>
///
/// If the retry delay is greater than zero, <see cref="Thread.Sleep(int)"/> is called
/// between retry attempts.
/// </remarks>
public class RequestRetryLogic : IRetryLogic<Response, HttpStatusCode>
{
/// <inheritdoc/>
public Response Execute(Func<Response> callback, int retryCount = 0, TimeSpan? retryDelay = null)
{
if (callback == null)
throw new ArgumentNullException("callback");
if (retryCount < 0)
throw new ArgumentOutOfRangeException("retryCount");
if (retryDelay.HasValue && retryDelay < TimeSpan.Zero)
throw new ArgumentOutOfRangeException("retryDelay");
return Execute(callback, Enumerable.Empty<HttpStatusCode>(), retryCount, retryDelay);
}
/// <inheritdoc/>
public Response Execute(Func<Response> callback, IEnumerable<HttpStatusCode> non200SuccessCodes, int retryCount = 0, TimeSpan? retryDelay = null)
{
if (callback == null)
throw new ArgumentNullException("callback");
if (retryCount < 0)
throw new ArgumentOutOfRangeException("retryCount");
if (retryDelay.HasValue && retryDelay < TimeSpan.Zero)
throw new ArgumentOutOfRangeException("retryDelay");
Response response;
do
{
response = callback();
if (IsRequestSuccessful(response, non200SuccessCodes))
return response;
retryCount--;
if (retryCount >= 0)
Thread.Sleep(retryDelay ?? TimeSpan.Zero);
}
while (retryCount >= 0);
return response;
}
/// <summary>
/// Checks if <paramref name="response"/> is considered a successful HTTP response.
/// </summary>
/// <param name="response">The response.</param>
/// <param name="non200SuccessCodes">The HTTP status codes to consider successful, in addition to codes
/// below 300.</param>
/// <returns><c>true</c> if <paramref name="response"/> is considered a successful HTTP
/// response, otherwise <c>false</c>.</returns>
private static bool IsRequestSuccessful(Response response, IEnumerable<HttpStatusCode> non200SuccessCodes)
{
if (response != null && response.StatusCode < (HttpStatusCode)300)
return true;
if (non200SuccessCodes == null)
return false;
return non200SuccessCodes.Contains(response.StatusCode);
}
}
}

View File

@ -0,0 +1,17 @@
using System;
namespace JSIStudios.SimpleRESTServices.Core.Exceptions
{
public class BadWebRequestException : Exception
{
public BadWebRequestException(string message)
: base(message)
{
}
public BadWebRequestException(string message, Exception innerException)
: base(message, innerException)
{
}
}
}

View File

@ -0,0 +1,23 @@
using System;
namespace JSIStudios.SimpleRESTServices.Core.Exceptions
{
public class HttpHeaderNotFoundException : Exception
{
public string Name { get; set; }
public HttpHeaderNotFoundException(string name, string message)
: base(message)
{
Name = name;
}
public HttpHeaderNotFoundException(string name)
{
Name = name;
}
public HttpHeaderNotFoundException()
{
}
}
}

View File

@ -0,0 +1,12 @@
using System;
namespace JSIStudios.SimpleRESTServices.Core.Exceptions
{
public class HttpResourceNotFoundException : Exception
{
public HttpResourceNotFoundException(string message)
: base(message)
{
}
}
}

View File

@ -0,0 +1,11 @@
using System;
namespace JSIStudios.SimpleRESTServices.Core.Exceptions
{
public class HttpResourceNotModifiedException : Exception
{
public HttpResourceNotModifiedException()
{
}
}
}

View File

@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using JSIStudios.SimpleRESTServices.Client;
namespace JSIStudios.SimpleRESTServices.Core
{
/// <summary>
/// Represents custom logging behavior for a REST request.
/// </summary>
public interface IRequestLogger
{
/// <summary>
/// Logs a REST request along with its response.
/// </summary>
/// <param name="httpMethod">The <see cref="HttpMethod"/> used for the request.</param>
/// <param name="uri">The complete URI, including the query string (if any).</param>
/// <param name="requestHeaders">The set of custom headers sent with the request. This may be <c>null</c> if no custom headers were specified.</param>
/// <param name="requestBody">The body of the request. This is <c>null</c> or empty if the request did not include a body.</param>
/// <param name="response">The response.</param>
/// <param name="requestStartTime">The request start time.</param>
/// <param name="requestEndTime">The request end time.</param>
/// <param name="extendedData">The user-defined extended data specified in <see cref="RequestSettings.ExtendedLoggingData"/>.</param>
void Log(HttpMethod httpMethod, string uri, Dictionary<string, string> requestHeaders, string requestBody, Response response, DateTimeOffset requestStartTime, DateTimeOffset requestEndTime, Dictionary<string, string> extendedData);
}
}

View File

@ -0,0 +1,20 @@
using System;
using System.Collections.Specialized;
using System.Net;
using JSIStudios.SimpleRESTServices.Server.EventArgs;
namespace JSIStudios.SimpleRESTServices.Core
{
public interface IRequestProcessor
{
event EventHandler<RESTRequestStartedEventArgs> RequestStarted;
event EventHandler<RESTRequestCompletedEventArgs> RequestCompleted;
event EventHandler<RESTRequestErrorEventArgs> OnError;
void Execute(Action<Guid> callBack, HttpStatusCode successStatus = HttpStatusCode.OK);
void Execute(Action<Guid> callBack, NameValueCollection responseHeaders, HttpStatusCode successStatus = HttpStatusCode.OK);
TResult Execute<TResult>(Func<Guid, TResult> callBack, HttpStatusCode successStatus = HttpStatusCode.OK);
TResult Execute<TResult>(Func<Guid, TResult> callBack, NameValueCollection responseHeaders, HttpStatusCode successStatus = HttpStatusCode.OK);
}
}

View File

@ -0,0 +1,585 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using JSIStudios.SimpleRESTServices.Client.Json;
namespace JSIStudios.SimpleRESTServices.Client
{
/// <summary>
/// Represents a service for executing generic REST requests.
/// </summary>
public interface IRestService
{
/// <summary>
/// Executes a REST request with a strongly-typed <paramref name="body"/> and result.
/// </summary>
/// <typeparam name="T">The type of the data returned in the REST response.</typeparam>
/// <typeparam name="TBody">The type of the data included in the body of the REST request.</typeparam>
/// <param name="url">The base URI.</param>
/// <param name="method">The HTTP method to use for the request.</param>
/// <param name="body">
/// The strongly-typed data to include in the body of the request. If the value is <c>null</c>,
/// the behavior is implementation-defined.
/// </param>
/// <param name="headers">
/// A collection of custom HTTP headers to include with the request. This parameter is
/// optional. If the value is <c>null</c>, no custom headers are added to the HTTP request.
/// </param>
/// <param name="queryStringParameters">
/// A collection of parameters to add to the query string portion of the request URI.
/// This parameter is optional. If the value is <c>null</c>, no parameters are added
/// to the query string.
/// </param>
/// <param name="settings">
/// The settings to use for the request. This parameters is optional. If the value is
/// <c>null</c>, an implementation-specific set of default settings will be used for the request.
/// </param>
/// <returns>Returns a <see cref="Response{T}"/> object containing the HTTP status code, headers, body,
/// and strongly-typed data from the REST response.</returns>
/// <exception cref="ArgumentNullException">If <paramref name="url"/> is <c>null</c>.</exception>
/// <exception cref="UriFormatException">If <paramref name="url"/> is not a valid base URI.</exception>
/// <exception cref="NotSupportedException">If <paramref name="method"/> is not supported by the service.</exception>
/// <exception cref="StringSerializationException">
/// If the body of the response could not be deserialized to an object of type <typeparamref name="T"/>.
/// </exception>
Response<T> Execute<T, TBody>(
String url,
HttpMethod method,
TBody body,
Dictionary<string, string> headers = null,
Dictionary<string, string> queryStringParameters = null,
RequestSettings settings = null);
/// <summary>
/// Executes a REST request with a strongly-typed <paramref name="body"/> and result.
/// </summary>
/// <typeparam name="T">The type of the data returned in the REST response.</typeparam>
/// <typeparam name="TBody">The type of the data included in the body of the REST request.</typeparam>
/// <param name="url">The base URI.</param>
/// <param name="method">The HTTP method to use for the request.</param>
/// <param name="body">
/// The strongly-typed data to include in the body of the request. If the value is <c>null</c>,
/// the behavior is implementation-defined.
/// </param>
/// <param name="headers">
/// A collection of custom HTTP headers to include with the request. This parameter is
/// optional. If the value is <c>null</c>, no custom headers are added to the HTTP request.
/// </param>
/// <param name="queryStringParameters">
/// A collection of parameters to add to the query string portion of the request URI.
/// This parameter is optional. If the value is <c>null</c>, no parameters are added
/// to the query string.
/// </param>
/// <param name="settings">
/// The settings to use for the request. This parameters is optional. If the value is
/// <c>null</c>, an implementation-specific set of default settings will be used for the request.
/// </param>
/// <returns>Returns a <see cref="Response{T}"/> object containing the HTTP status code, headers, body,
/// and strongly-typed data from the REST response.</returns>
/// <exception cref="ArgumentNullException">If <paramref name="url"/> is <c>null</c>.</exception>
/// <exception cref="NotSupportedException">If <paramref name="method"/> is not supported by the service.</exception>
/// <exception cref="StringSerializationException">
/// If the body of the response could not be deserialized to an object of type <typeparamref name="T"/>.
/// </exception>
Response<T> Execute<T, TBody>(
Uri url,
HttpMethod method,
TBody body,
Dictionary<string, string> headers = null,
Dictionary<string, string> queryStringParameters = null,
RequestSettings settings = null);
/// <summary>
/// Executes a REST request with a string <paramref name="body"/> and strongly-typed result.
/// </summary>
/// <typeparam name="T">The type of the data returned in the REST response.</typeparam>
/// <param name="url">The base URI.</param>
/// <param name="method">The HTTP method to use for the request.</param>
/// <param name="body">
/// The body of the request. This parameter is optional. If the value is <c>null</c>,
/// the request is sent without a body.
/// </param>
/// <param name="headers">
/// A collection of custom HTTP headers to include with the request. This parameter is
/// optional. If the value is <c>null</c>, no custom headers are added to the HTTP request.
/// </param>
/// <param name="queryStringParameters">
/// A collection of parameters to add to the query string portion of the request URI.
/// This parameter is optional. If the value is <c>null</c>, no parameters are added
/// to the query string.
/// </param>
/// <param name="settings">
/// The settings to use for the request. This parameters is optional. If the value is
/// <c>null</c>, an implementation-specific set of default settings will be used for the request.
/// </param>
/// <returns>Returns a <see cref="Response{T}"/> object containing the HTTP status code, headers, body,
/// and strongly-typed data from the REST response.</returns>
/// <exception cref="ArgumentNullException">If <paramref name="url"/> is <c>null</c>.</exception>
/// <exception cref="UriFormatException">If <paramref name="url"/> is not a valid base URI.</exception>
/// <exception cref="NotSupportedException">If <paramref name="method"/> is not supported by the service.</exception>
/// <exception cref="StringSerializationException">
/// If the body of the response could not be deserialized to an object of type <typeparamref name="T"/>.
/// </exception>
Response<T> Execute<T>(
String url,
HttpMethod method,
string body = null,
Dictionary<string, string> headers = null,
Dictionary<string, string> queryStringParameters = null,
RequestSettings settings = null);
/// <summary>
/// Executes a REST request with a string <paramref name="body"/> and strongly-typed result.
/// </summary>
/// <typeparam name="T">The type of the data returned in the REST response.</typeparam>
/// <param name="url">The base URI.</param>
/// <param name="method">The HTTP method to use for the request.</param>
/// <param name="body">
/// The body of the request. This parameter is optional. If the value is <c>null</c>,
/// the request is sent without a body.
/// </param>
/// <param name="headers">
/// A collection of custom HTTP headers to include with the request. This parameter is
/// optional. If the value is <c>null</c>, no custom headers are added to the HTTP request.
/// </param>
/// <param name="queryStringParameters">
/// A collection of parameters to add to the query string portion of the request URI.
/// This parameter is optional. If the value is <c>null</c>, no parameters are added
/// to the query string.
/// </param>
/// <param name="settings">
/// The settings to use for the request. This parameters is optional. If the value is
/// <c>null</c>, an implementation-specific set of default settings will be used for the request.
/// </param>
/// <returns>Returns a <see cref="Response{T}"/> object containing the HTTP status code, headers, body,
/// and strongly-typed data from the REST response.</returns>
/// <exception cref="ArgumentNullException">If <paramref name="url"/> is <c>null</c>.</exception>
/// <exception cref="NotSupportedException">If <paramref name="method"/> is not supported by the service.</exception>
/// <exception cref="StringSerializationException">
/// If the body of the response could not be deserialized to an object of type <typeparamref name="T"/>.
/// </exception>
Response<T> Execute<T>(
Uri url,
HttpMethod method,
string body = null,
Dictionary<string, string> headers = null,
Dictionary<string, string> queryStringParameters = null,
RequestSettings settings = null);
/// <summary>
/// Executes a REST request with a strongly-typed <paramref name="body"/> and basic result (text or no content).
/// </summary>
/// <typeparam name="TBody">The type of the data included in the body of the REST request.</typeparam>
/// <param name="url">The base URI.</param>
/// <param name="method">The HTTP method to use for the request.</param>
/// <param name="body">
/// The strongly-typed data to include in the body of the request. If the value is <c>null</c>,
/// the behavior is implementation-defined.
/// </param>
/// <param name="headers">
/// A collection of custom HTTP headers to include with the request. This parameter is
/// optional. If the value is <c>null</c>, no custom headers are added to the HTTP request.
/// </param>
/// <param name="queryStringParameters">
/// A collection of parameters to add to the query string portion of the request URI.
/// This parameter is optional. If the value is <c>null</c>, no parameters are added
/// to the query string.
/// </param>
/// <param name="settings">
/// The settings to use for the request. This parameters is optional. If the value is
/// <c>null</c>, an implementation-specific set of default settings will be used for the request.
/// </param>
/// <returns>Returns a <see cref="Response"/> object containing the HTTP status code, headers,
/// and body from the REST response.</returns>
/// <exception cref="ArgumentNullException">If <paramref name="url"/> is <c>null</c>.</exception>
/// <exception cref="UriFormatException">If <paramref name="url"/> is not a valid base URI.</exception>
/// <exception cref="NotSupportedException">If <paramref name="method"/> is not supported by the service.</exception>
Response Execute<TBody>(
String url,
HttpMethod method,
TBody body,
Dictionary<string, string> headers = null,
Dictionary<string, string> queryStringParameters = null,
RequestSettings settings = null);
/// <summary>
/// Executes a REST request with a strongly-typed <paramref name="body"/> and basic result (text or no content).
/// </summary>
/// <typeparam name="TBody">The type of the data included in the body of the REST request.</typeparam>
/// <param name="url">The base URI.</param>
/// <param name="method">The HTTP method to use for the request.</param>
/// <param name="body">
/// The strongly-typed data to include in the body of the request. If the value is <c>null</c>,
/// the behavior is implementation-defined.
/// </param>
/// <param name="headers">
/// A collection of custom HTTP headers to include with the request. This parameter is
/// optional. If the value is <c>null</c>, no custom headers are added to the HTTP request.
/// </param>
/// <param name="queryStringParameters">
/// A collection of parameters to add to the query string portion of the request URI.
/// This parameter is optional. If the value is <c>null</c>, no parameters are added
/// to the query string.
/// </param>
/// <param name="settings">
/// The settings to use for the request. This parameters is optional. If the value is
/// <c>null</c>, an implementation-specific set of default settings will be used for the request.
/// </param>
/// <returns>Returns a <see cref="Response"/> object containing the HTTP status code, headers,
/// and body from the REST response.</returns>
/// <exception cref="ArgumentNullException">If <paramref name="url"/> is <c>null</c>.</exception>
/// <exception cref="NotSupportedException">If <paramref name="method"/> is not supported by the service.</exception>
Response Execute<TBody>(
Uri url,
HttpMethod method,
TBody body,
Dictionary<string, string> headers = null,
Dictionary<string, string> queryStringParameters = null,
RequestSettings settings = null);
/// <summary>
/// Executes a REST request with a string <paramref name="body"/> and basic result (text or no content).
/// </summary>
/// <param name="url">The base URI.</param>
/// <param name="method">The HTTP method to use for the request.</param>
/// <param name="body">
/// The body of the request. This parameter is optional. If the value is <c>null</c>,
/// the request is sent without a body.
/// </param>
/// <param name="headers">
/// A collection of custom HTTP headers to include with the request. This parameter is
/// optional. If the value is <c>null</c>, no custom headers are added to the HTTP request.
/// </param>
/// <param name="queryStringParameters">
/// A collection of parameters to add to the query string portion of the request URI.
/// This parameter is optional. If the value is <c>null</c>, no parameters are added
/// to the query string.
/// </param>
/// <param name="settings">
/// The settings to use for the request. This parameters is optional. If the value is
/// <c>null</c>, an implementation-specific set of default settings will be used for the request.
/// </param>
/// <returns>Returns a <see cref="Response"/> object containing the HTTP status code, headers,
/// and body from the REST response.</returns>
/// <exception cref="ArgumentNullException">If <paramref name="url"/> is <c>null</c>.</exception>
/// <exception cref="UriFormatException">If <paramref name="url"/> is not a valid base URI.</exception>
/// <exception cref="NotSupportedException">If <paramref name="method"/> is not supported by the service.</exception>
Response Execute(
String url,
HttpMethod method,
string body = null,
Dictionary<string, string> headers = null,
Dictionary<string, string> queryStringParameters = null,
RequestSettings settings = null);
/// <summary>
/// Executes a REST request with a string <paramref name="body"/> and basic result (text or no content).
/// </summary>
/// <param name="url">The base URI.</param>
/// <param name="method">The HTTP method to use for the request.</param>
/// <param name="body">
/// The body of the request. This parameter is optional. If the value is <c>null</c>,
/// the request is sent without a body.
/// </param>
/// <param name="headers">
/// A collection of custom HTTP headers to include with the request. This parameter is
/// optional. If the value is <c>null</c>, no custom headers are added to the HTTP request.
/// </param>
/// <param name="queryStringParameters">
/// A collection of parameters to add to the query string portion of the request URI.
/// This parameter is optional. If the value is <c>null</c>, no parameters are added
/// to the query string.
/// </param>
/// <param name="settings">
/// The settings to use for the request. This parameters is optional. If the value is
/// <c>null</c>, an implementation-specific set of default settings will be used for the request.
/// </param>
/// <returns>Returns a <see cref="Response"/> object containing the HTTP status code, headers,
/// and body from the REST response.</returns>
/// <exception cref="ArgumentNullException">If <paramref name="url"/> is <c>null</c>.</exception>
/// <exception cref="NotSupportedException">If <paramref name="method"/> is not supported by the service.</exception>
Response Execute(
Uri url,
HttpMethod method,
string body = null,
Dictionary<string, string> headers = null,
Dictionary<string, string> queryStringParameters = null,
RequestSettings settings = null);
/// <summary>
/// Executes a REST request with a <see cref="System.IO.Stream"/> <paramref name="content"/> and strongly-typed result.
/// </summary>
/// <typeparam name="T">The type of the data returned in the REST response.</typeparam>
/// <param name="url">The base URI.</param>
/// <param name="method">The HTTP method to use for the request.</param>
/// <param name="content">A stream providing the body of the request.</param>
/// <param name="bufferSize">
/// The size of the buffer used for copying data from <paramref name="content"/> to the
/// HTTP request stream.
/// </param>
/// <param name="maxReadLength">
/// The maximum number of bytes to send with the request. This parameter is optional.
/// If the value is 0, the request will include all data from <paramref name="content"/>.
/// </param>
/// <param name="headers">
/// A collection of custom HTTP headers to include with the request. This parameter is
/// optional. If the value is <c>null</c>, no custom headers are added to the HTTP request.
/// </param>
/// <param name="queryStringParameters">
/// A collection of parameters to add to the query string portion of the request URI.
/// This parameter is optional. If the value is <c>null</c>, no parameters are added
/// to the query string.
/// </param>
/// <param name="settings">
/// The settings to use for the request. This parameters is optional. If the value is
/// <c>null</c>, an implementation-specific set of default settings will be used for the request.
/// </param>
/// <param name="progressUpdated">
/// A user-defined callback function for reporting progress of the send operation.
/// This parameter is optional. If the value is <c>null</c>, the method does not report
/// progress updates to the caller.
/// </param>
/// <returns>Returns a <see cref="Response{T}"/> object containing the HTTP status code, headers, body,
/// and strongly-typed data from the REST response.</returns>
/// <exception cref="ArgumentNullException">
/// If <paramref name="url"/> is <c>null</c>.
/// <para>-or-</para>
/// <para>If <paramref name="content"/> is <c>null</c>.</para>
/// </exception>
/// <exception cref="ArgumentOutOfRangeException">
/// If <paramref name="bufferSize"/> is less than or equal to zero.
/// <para>-or-</para>
/// <para>If <paramref name="maxReadLength"/> is less than zero.</para>
/// </exception>
/// <exception cref="NotSupportedException">If <paramref name="method"/> is not supported by the service.</exception>
/// <exception cref="StringSerializationException">
/// If the body of the response could not be deserialized to an object of type <typeparamref name="T"/>.
/// </exception>
Response<T> Stream<T>(
Uri url,
HttpMethod method,
Stream content,
int bufferSize,
long maxReadLength = 0,
Dictionary<string, string> headers = null,
Dictionary<string, string> queryStringParameters = null,
RequestSettings settings = null,
Action<long> progressUpdated = null);
/// <summary>
/// Executes a REST request with a <see cref="System.IO.Stream"/> <paramref name="content"/> and basic result (text or no content).
/// </summary>
/// <param name="url">The base URI.</param>
/// <param name="method">The HTTP method to use for the request.</param>
/// <param name="content">A stream providing the body of the request.</param>
/// <param name="bufferSize">
/// The size of the buffer used for copying data from <paramref name="content"/> to the
/// HTTP request stream.
/// </param>
/// <param name="maxReadLength">
/// The maximum number of bytes to send with the request. This parameter is optional.
/// If the value is 0, the request will include all data from <paramref name="content"/>.
/// </param>
/// <param name="headers">
/// A collection of custom HTTP headers to include with the request. This parameter is
/// optional. If the value is <c>null</c>, no custom headers are added to the HTTP request.
/// </param>
/// <param name="queryStringParameters">
/// A collection of parameters to add to the query string portion of the request URI.
/// This parameter is optional. If the value is <c>null</c>, no parameters are added
/// to the query string.
/// </param>
/// <param name="settings">
/// The settings to use for the request. This parameters is optional. If the value is
/// <c>null</c>, an implementation-specific set of default settings will be used for the request.
/// </param>
/// <param name="progressUpdated">
/// A user-defined callback function for reporting progress of the send operation.
/// This parameter is optional. If the value is <c>null</c>, the method does not report
/// progress updates to the caller.
/// </param>
/// <returns>Returns a <see cref="Response"/> object containing the HTTP status code, headers,
/// and body from the REST response.</returns>
/// <exception cref="ArgumentNullException">
/// If <paramref name="url"/> is <c>null</c>.
/// <para>-or-</para>
/// <para>If <paramref name="content"/> is <c>null</c>.</para>
/// </exception>
/// <exception cref="ArgumentOutOfRangeException">
/// If <paramref name="bufferSize"/> is less than or equal to zero.
/// <para>-or-</para>
/// <para>If <paramref name="maxReadLength"/> is less than zero.</para>
/// </exception>
/// <exception cref="NotSupportedException">If <paramref name="method"/> is not supported by the service.</exception>
Response Stream(
Uri url,
HttpMethod method,
Stream content,
int bufferSize,
long maxReadLength = 0,
Dictionary<string, string> headers = null,
Dictionary<string, string> queryStringParameters = null,
RequestSettings settings = null,
Action<long> progressUpdated = null);
/// <summary>
/// Executes a REST request with a <see cref="System.IO.Stream"/> <paramref name="content"/> and strongly-typed result.
/// </summary>
/// <typeparam name="T">The type of the data returned in the REST response.</typeparam>
/// <param name="url">The base URI.</param>
/// <param name="method">The HTTP method to use for the request.</param>
/// <param name="content">A stream providing the body of the request.</param>
/// <param name="bufferSize">
/// The size of the buffer used for copying data from <paramref name="content"/> to the
/// HTTP request stream.
/// </param>
/// <param name="maxReadLength">
/// The maximum number of bytes to send with the request. This parameter is optional.
/// If the value is 0, the request will include all data from <paramref name="content"/>.
/// </param>
/// <param name="headers">
/// A collection of custom HTTP headers to include with the request. This parameter is
/// optional. If the value is <c>null</c>, no custom headers are added to the HTTP request.
/// </param>
/// <param name="queryStringParameters">
/// A collection of parameters to add to the query string portion of the request URI.
/// This parameter is optional. If the value is <c>null</c>, no parameters are added
/// to the query string.
/// </param>
/// <param name="settings">
/// The settings to use for the request. This parameters is optional. If the value is
/// <c>null</c>, an implementation-specific set of default settings will be used for the request.
/// </param>
/// <param name="progressUpdated">
/// A user-defined callback function for reporting progress of the send operation.
/// This parameter is optional. If the value is <c>null</c>, the method does not report
/// progress updates to the caller.
/// </param>
/// <returns>Returns a <see cref="Response{T}"/> object containing the HTTP status code, headers, body,
/// and strongly-typed data from the REST response.</returns>
/// <exception cref="ArgumentNullException">
/// If <paramref name="url"/> is <c>null</c>.
/// <para>-or-</para>
/// <para>If <paramref name="content"/> is <c>null</c>.</para>
/// </exception>
/// <exception cref="ArgumentOutOfRangeException">
/// If <paramref name="bufferSize"/> is less than or equal to zero.
/// <para>-or-</para>
/// <para>If <paramref name="maxReadLength"/> is less than zero.</para>
/// </exception>
/// <exception cref="UriFormatException">If <paramref name="url"/> is not a valid base URI.</exception>
/// <exception cref="NotSupportedException">If <paramref name="method"/> is not supported by the service.</exception>
/// <exception cref="StringSerializationException">
/// If the body of the response could not be deserialized to an object of type <typeparamref name="T"/>.
/// </exception>
Response<T> Stream<T>(
string url,
HttpMethod method,
Stream content,
int bufferSize,
long maxReadLength = 0,
Dictionary<string, string> headers = null,
Dictionary<string, string> queryStringParameters = null,
RequestSettings settings = null,
Action<long> progressUpdated = null);
/// <summary>
/// Executes a REST request with a <see cref="System.IO.Stream"/> <paramref name="content"/> and basic result (text or no content).
/// </summary>
/// <param name="url">The base URI.</param>
/// <param name="method">The HTTP method to use for the request.</param>
/// <param name="content">A stream providing the body of the request.</param>
/// <param name="bufferSize">
/// The size of the buffer used for copying data from <paramref name="content"/> to the
/// HTTP request stream.
/// </param>
/// <param name="maxReadLength">
/// The maximum number of bytes to send with the request. This parameter is optional.
/// If the value is 0, the request will include all data from <paramref name="content"/>.
/// </param>
/// <param name="headers">
/// A collection of custom HTTP headers to include with the request. This parameter is
/// optional. If the value is <c>null</c>, no custom headers are added to the HTTP request.
/// </param>
/// <param name="queryStringParameters">
/// A collection of parameters to add to the query string portion of the request URI.
/// This parameter is optional. If the value is <c>null</c>, no parameters are added
/// to the query string.
/// </param>
/// <param name="settings">
/// The settings to use for the request. This parameters is optional. If the value is
/// <c>null</c>, an implementation-specific set of default settings will be used for the request.
/// </param>
/// <param name="progressUpdated">
/// A user-defined callback function for reporting progress of the send operation.
/// This parameter is optional. If the value is <c>null</c>, the method does not report
/// progress updates to the caller.
/// </param>
/// <returns>Returns a <see cref="Response"/> object containing the HTTP status code, headers,
/// and body from the REST response.</returns>
/// <exception cref="ArgumentNullException">
/// If <paramref name="url"/> is <c>null</c>.
/// <para>-or-</para>
/// <para>If <paramref name="content"/> is <c>null</c>.</para>
/// </exception>
/// <exception cref="ArgumentOutOfRangeException">
/// If <paramref name="bufferSize"/> is less than or equal to zero.
/// <para>-or-</para>
/// <para>If <paramref name="maxReadLength"/> is less than zero.</para>
/// </exception>
/// <exception cref="UriFormatException">If <paramref name="url"/> is not a valid base URI.</exception>
/// <exception cref="NotSupportedException">If <paramref name="method"/> is not supported by the service.</exception>
Response Stream(
string url,
HttpMethod method,
Stream content,
int bufferSize,
long maxReadLength = 0,
Dictionary<string, string> headers = null,
Dictionary<string, string> queryStringParameters = null,
RequestSettings settings = null,
Action<long> progressUpdated = null);
/// <summary>
/// Executes a REST request with a string <paramref name="body"/> and user-defined
/// callback function for constructing the resulting <see cref="Response"/> object.
/// </summary>
/// <remarks>
/// The Boolean argument to <paramref name="responseBuilderCallback"/> indicates whether
/// or not an exception was thrown while executing the request. The value is <c>true</c>
/// if an exception occurred, otherwise <c>false</c>.
/// </remarks>
/// <param name="url">The base URI.</param>
/// <param name="method">The HTTP method to use for the request.</param>
/// <param name="responseBuilderCallback">A user-specified function used to construct the resulting <see cref="Response"/>
/// object from the <see cref="HttpWebResponse"/> and a Boolean value specifying whether or not a <see cref="WebException"/>
/// was thrown during the request. If this value is <c>null</c>, this method is equivalent to calling
/// <see cref="Execute(Uri, HttpMethod, string, Dictionary{string, string}, Dictionary{string, string}, RequestSettings)"/>.</param>
/// <param name="body">The body of the request. If the value is <c>null</c>, the request is sent without a body.</param>
/// <param name="headers">
/// A collection of custom HTTP headers to include with the request. If the value is
/// <c>null</c>, no custom headers are added to the HTTP request.
/// </param>
/// <param name="queryStringParameters">
/// A collection of parameters to add to the query string portion of the request URI.
/// If the value is <c>null</c>, no parameters are added to the query string.
/// </param>
/// <param name="settings">
/// The settings to use for the request. If the value is <c>null</c>, an implementation-specific
/// set of default settings will be used for the request.
/// </param>
/// <returns>Returns a <see cref="Response"/> object containing the HTTP status code, headers,
/// and body from the REST response.</returns>
/// <exception cref="ArgumentNullException">If <paramref name="url"/> is <c>null</c>.</exception>
/// <exception cref="NotSupportedException">If <paramref name="method"/> is not supported by the service.</exception>
Response Execute(
Uri url,
HttpMethod method,
Func<HttpWebResponse, bool, Response> responseBuilderCallback,
string body,
Dictionary<string, string> headers,
Dictionary<string, string> queryStringParameters,
RequestSettings settings);
}
}

View File

@ -0,0 +1,55 @@
using System;
using System.Collections.Generic;
namespace JSIStudios.SimpleRESTServices.Core
{
/// <summary>
/// Provides the behavior for executing a callback method with configurable success
/// values, number of retries, and the retry delay.
/// </summary>
/// <typeparam name="T">The operation return type</typeparam>
/// <typeparam name="T2">The type of the value used to represent the operation's success or failure</typeparam>
#if NET35
public interface IRetryLogic<T, T2>
#else
public interface IRetryLogic<T, in T2>
#endif
{
/// <summary>
/// Executes a user-defined operation with the specified number of retry attempts
/// if a failure occurs and delay time between retry attempts.
/// </summary>
/// <param name="logic">The user-defined operation to execute.</param>
/// <param name="retryCount">The number of times to retry a failed operation. This parameter is optional. The default value is 1.</param>
/// <param name="retryDelay">The delay between retry operations. This parameter is optional. If the value is <c>null</c>, the default is <see cref="TimeSpan.Zero"/> (no delay).</param>
/// <returns>Returns the result of a successful execution of <paramref name="logic"/>. If
/// <paramref name="logic"/> failed and the maximum number of retries has been reached,
/// the method returns the last (unsuccessful) result returned by <paramref name="logic"/>.</returns>
/// <exception cref="ArgumentNullException">If <paramref name="logic"/> is <c>null</c>.</exception>
/// <exception cref="ArgumentOutOfRangeException">
/// If <paramref name="retryCount"/> is less than zero.
/// <para>-or-</para>
/// <para>If <paramref name="retryDelay"/> is less than <see cref="TimeSpan.Zero"/>.</para>
/// </exception>
T Execute(Func<T> logic, int retryCount = 0, TimeSpan? retryDelay = null);
/// <summary>
/// Executes a user-defined operation with the specified "success" values, number of
/// retry attempts if a failure occurs, and delay time between retry attempts.
/// </summary>
/// <param name="logic">The user-defined operation to execute.</param>
/// <param name="successValues">A collection of values which are generally considered failures, but should be treated as success values for this call.</param>
/// <param name="retryCount">The number of times to retry a failed operation. This parameter is optional. The default value is 1.</param>
/// <param name="retryDelay">The delay between retry operations. This parameter is optional. If the value is <c>null</c>, the default is <see cref="TimeSpan.Zero"/> (no delay).</param>
/// <returns>Returns the result of a successful execution of <paramref name="logic"/>. If
/// <paramref name="logic"/> failed and the maximum number of retries has been reached,
/// the method returns the last (unsuccessful) result returned by <paramref name="logic"/>.</returns>
/// <exception cref="ArgumentNullException">If <paramref name="logic"/> is <c>null</c>.</exception>
/// <exception cref="ArgumentOutOfRangeException">
/// If <paramref name="retryCount"/> is less than zero.
/// <para>-or-</para>
/// <para>If <paramref name="retryDelay"/> is less than <see cref="TimeSpan.Zero"/>.</para>
/// </exception>
T Execute(Func<T> logic, IEnumerable<T2> successValues, int retryCount = 0, TimeSpan? retryDelay = null);
}
}

View File

@ -0,0 +1,7 @@
namespace JSIStudios.SimpleRESTServices.Core
{
public interface ITextCleaner
{
string Clean(string text);
}
}

View File

@ -0,0 +1,31 @@
using System;
using System.Collections.Generic;
namespace JSIStudios.SimpleRESTServices.Core
{
/// <summary>
/// Represents a builder which can construct a complete URI for a GET or HEAD request
/// from a base URI and a collection of query parameters.
/// </summary>
public interface IUrlBuilder
{
/// <summary>
/// Constructs a complete URI for an HTTP request using a base URI and a
/// collection of query string parameters.
/// </summary>
/// <remarks>
/// If <paramref name="baseUrl"/> already contains a query string, the specified
/// <paramref name="queryStringParameters"/> are appended to the existing query string.
/// This method does not perform substitution for any template parameters
/// which may exist in <paramref name="baseUrl"/>. If <paramref name="queryStringParameters"/>
/// is <c>null</c> or empty, <paramref name="baseUrl"/> is returned unchanged.
/// </remarks>
/// <param name="baseUrl">The base URI.</param>
/// <param name="queryStringParameters">A collection of parameters to place in the URI query string,
/// or <c>null</c> if there are no parameters.</param>
/// <returns>A <see cref="Uri"/> constructed from <paramref name="baseUrl"/> and the specified
/// <paramref name="queryStringParameters"/>.</returns>
/// <exception cref="ArgumentNullException">If <paramref name="baseUrl"/> is <c>null</c>.</exception>
Uri Build(Uri baseUrl, Dictionary<string, string> queryStringParameters);
}
}

View File

@ -0,0 +1,30 @@
using System;
using System.Reflection;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("SimpleRESTServices")]
[assembly: AssemblyDescription("A simple set of client side and server side REST helpers")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("JSI Studios")]
[assembly: AssemblyProduct("SimpleRESTServices")]
[assembly: AssemblyCopyright("Copyright © JSI Studios 2013")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: CLSCompliant(true)]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("5a741815-e26d-4cb0-a42b-78f7c1611190")]
// Refer to the following issue before changing these version numbers:
// https://github.com/JSIStudios/SimpleRestServices/issues/53
[assembly: AssemblyVersion("1.3.0.0")]
[assembly: AssemblyFileVersion("1.3.0.3")]
[assembly: AssemblyInformationalVersion("1.3.0.3-dev")]

View File

@ -0,0 +1,22 @@
using System;
namespace JSIStudios.SimpleRESTServices.Server.EventArgs
{
public class RESTRequestCompletedEventArgs : System.EventArgs
{
public Guid RequestId { get; private set; }
public object Response { get; private set; }
public TimeSpan ExecutionTime { get; private set; }
public RESTRequestCompletedEventArgs(Guid requestId, object response, TimeSpan executionTime)
: base()
{
RequestId = requestId;
Response = response;
ExecutionTime = executionTime;
}
}
}

View File

@ -0,0 +1,18 @@
using System;
namespace JSIStudios.SimpleRESTServices.Server.EventArgs
{
public class RESTRequestErrorEventArgs : System.EventArgs
{
public Guid RequestId { get; private set; }
public Exception Error { get; private set; }
public RESTRequestErrorEventArgs(Guid requestId, Exception error)
: base()
{
RequestId = requestId;
Error = error;
}
}
}

View File

@ -0,0 +1,18 @@
using System;
namespace JSIStudios.SimpleRESTServices.Server.EventArgs
{
public class RESTRequestStartedEventArgs : System.EventArgs
{
public Guid RequestId { get; private set; }
public string Request { get; private set; }
public RESTRequestStartedEventArgs(Guid requestId, string request)
: base()
{
RequestId = requestId;
Request = request;
}
}
}

View File

@ -0,0 +1,219 @@
using System;
using System.Linq;
using System.Text.RegularExpressions;
using System.Web;
using JSIStudios.SimpleRESTServices.Core;
namespace JSIStudios.SimpleRESTServices.Server
{
public class FullHtmlTagCleaner : ITextCleaner
{
public static string Name = "FullHtmlTagCleaner";
private string[] _allowedTags = new string[] { };
private string[] _illegalTags = new string[] { "style", "script", "embed", "object" };
private static string[] _illegalCharacters = new[] { "&#0;", "&#x0;", "%0", "&#1;", "&#x1;", "%1", "&#2;", "&#x2;", "%2", "&#3;", "&#x3;", "%3", "&#4;", "&#x4;", "%4", "&#5;", "&#x5;", "%5", "&#6;", "&#x6;", "%6", "&#7;", "&#x7;", "%7", "&#8;", "&#x8;", "%8", "&#9;", "&#x9;", "%9", "&#10;", "&#xa;", "%a", "&#11;", "&#xb;", "%b", "&#12;", "&#xc;", "%c", "&#13;", "&#xd;", "%d", "&#14;", "&#xe;", "%e", "&#15;", "&#xf;", "%f", "&#16;", "&#x10;", "%10", "&#17;", "&#x11;", "%11", "&#18;", "&#x12;", "%12", "&#19;", "&#x13;", "%13", "&#20;", "&#x14;", "%14", "&#21;", "&#x15;", "%15", "&#22;", "&#x16;", "%16", "&#23;", "&#x17;", "%17", "&#24;", "&#x18;", "%18", "&#25;", "&#x19;", "%19", "&#26;", "&#x1a;", "%1a", "&#27;", "&#x1b;", "%1b", "&#28;", "&#x1c;", "%1c", "&#29;", "&#x1d;", "%1d", "&#30;", "&#x1e;", "%1e", "&#31;", "&#x1f;", "%1f", "&#127;", "&#x7f;", "%7f", "&#128;", "&#x80;", "%80", "&#129;", "&#x81;", "%81", "&#130;", "&#x82;", "%82", "&#131;", "&#x83;", "%83", "&#132;", "&#x84;", "%84", "&#133;", "&#x85;", "%85", "&#134;", "&#x86;", "%86", "&#135;", "&#x87;", "%87", "&#136;", "&#x88;", "%88", "&#137;", "&#x89;", "%89", "&#138;", "&#x8a;", "%8a", "&#139;", "&#x8b;", "%8b", "&#140;", "&#x8c;", "%8c", "&#141;", "&#x8d;", "%8d", "&#142;", "&#x8e;", "%8e", "&#143;", "&#x8f;", "%8f", "&#144;", "&#x90;", "%90", "&#145;", "&#x91;", "%91", "&#146;", "&#x92;", "%92", "&#147;", "&#x93;", "%93", "&#148;", "&#x94;", "%94", "&#149;", "&#x95;", "%95", "&#150;", "&#x96;", "%96", "&#151;", "&#x97;", "%97", "&#152;", "&#x98;", "%98", "&#153;", "&#x99;", "%99", "&#154;", "&#x9a;", "%9a", "&#155;", "&#x9b;", "%9b", "&#156;", "&#x9c;", "%9c", "&#157;", "&#x9d;", "%9d", "&#158;", "&#x9e;", "%9e", "&#159;", "&#x9f;", "%9f" };
public virtual string[] IllegalTags
{
get { return _illegalTags; }
set { _illegalTags = value; }
}
public virtual string[] AllowedTags
{
get { return _allowedTags; }
set { _allowedTags = value; }
}
public static string[] IllegalCharacters
{
get { return _illegalCharacters; }
set { _illegalCharacters = value; }
}
public string Clean(string source)
{
if (string.IsNullOrEmpty(source))
{
return null;
}
var allowedTags = AllowedTags;
var illegalTags = IllegalTags;
source = RemoveIllegalCharacters(source);
source = illegalTags.Where(illegalTag => illegalTag == "style" || illegalTag == "script").Aggregate(source, (current, illegalTag) => RemoveTagsAndTextBetweenTags("<" + illegalTag + ">", "</" + illegalTag + ">", current));
var array = new char[source.Length];
var arrayIndex = 0;
source = Regex.Replace(source, @"(\r\n)|(\r)|(\n)", " ").Replace("\t", "");
var sourceLen = source.Length;
for (int idx = 0; idx < sourceLen; idx++)
{
char let = source[idx];
if (let == '<')
{
var len = 0;
var idx2 = idx;
char let2;
string tag = string.Empty;
do
{
idx2++;
if (idx2 >= sourceLen)
{
tag = string.Empty;
break;
}
len++;
let2 = source[idx2];
if (string.IsNullOrEmpty(tag) && (let2 == ' ' || (let2 == '/' && len > 1)))
{
tag = source.Substring(idx + 1, len - 1);
}
} while (let2 != '>');
if (string.IsNullOrEmpty(tag))
{
int start = idx + 1;
if (source[start] == '/')
{
start++;
len--;
}
tag = source.Substring(start, len - 1);
}
if (allowedTags.Contains(tag.ToLower()))
{
for (int i = idx; i <= idx2; i++)
{
if (i >= sourceLen)
break;
array[arrayIndex] = source[i];
arrayIndex++;
}
}
idx = idx2;
if (illegalTags.Contains(tag.ToLower()))
{
do
{
idx++;
if (idx < (sourceLen - 1))
{
let = source[idx];
if (let == '<' && source[++idx] == '/')
{
len = 0;
idx2 = idx;
var endTag = string.Empty;
do
{
idx2++;
len++;
let2 = source[idx2];
} while (let2 != '>' && idx2 < sourceLen);
idx = idx2;
let = let2;
}
}
} while (let != '>' && idx < (sourceLen - 1));
}
}
else
{
array[arrayIndex] = let;
arrayIndex++;
}
}
//return new string(array, 0, arrayIndex);
var s = new string(array, 0, arrayIndex);
return HttpUtility.HtmlDecode(s.Trim());
}
private static string RemoveIllegalCharacters(string source)
{
var sortedArray = from s in IllegalCharacters
orderby s.Length descending
select s;
return sortedArray.Aggregate(source, (current, illegalCharacter) => current.Replace(illegalCharacter, ""));
}
private static string RemoveTagsAndTextBetweenTags(string startTag, string endTag, string strSource)
{
var temp = strSource;
// remove the text in start tag if any. eg - <script type='text/javascript'>var _sf_startpt=(new Date()).getTime()</script>
// replaces the tag with simple tags like <script>var _sf_startpt=(new Date()).getTime()</script>
while (temp.IndexOf(startTag.Substring(0, startTag.Length - 1), StringComparison.OrdinalIgnoreCase) != -1)
{
var result = GetTextAlongWithTag(startTag.Substring(0, startTag.Length - 1), ">", temp, true, true);
temp = !string.IsNullOrWhiteSpace(result[0]) ? strSource.Replace(result[0], string.Empty) : temp;
if (result[0] != startTag)
{
// to handle the tags which doesn't have closing tags. eg - <script src="/hive/javascripts/scriptaculous.js"/>
if (result[0].IndexOf("/>") != -1)
strSource = !string.IsNullOrWhiteSpace(result[0]) ? strSource.Replace(result[0], startTag + endTag) : strSource;
else
strSource = !string.IsNullOrWhiteSpace(result[0]) ? strSource.Replace(result[0], startTag) : strSource;
}
}
//declare safety int variable to prevent infinite loop if any use case is not covered.
var iSafetyCheck = 10000;
//remove all the tags along with the text in between the tags
while (strSource.IndexOf(startTag, StringComparison.OrdinalIgnoreCase) != -1)
{
if (--iSafetyCheck == 0) return strSource;
var result = GetTextAlongWithTag(startTag, endTag, strSource, true, true);
strSource = !string.IsNullOrWhiteSpace(result[0]) ? strSource.Replace(result[0], string.Empty) : strSource;
}
return strSource;
}
private static string[] GetTextAlongWithTag(string startTag, string endTag, string strSource, bool removeBegin, bool removeEnd)
{
string[] result = { string.Empty, string.Empty };
var iIndexOfBegin = strSource.IndexOf(startTag, StringComparison.OrdinalIgnoreCase);
if (iIndexOfBegin != -1)
{
if (removeBegin)
iIndexOfBegin -= startTag.Length;
strSource = strSource.Substring(iIndexOfBegin
+ startTag.Length);
var iEnd = strSource.IndexOf(endTag, StringComparison.OrdinalIgnoreCase);
if (iEnd != -1)
{
if (removeEnd)
iEnd += endTag.Length;
result[0] = strSource.Substring(0, iEnd);
if (iEnd + endTag.Length < strSource.Length)
result[1] = strSource.Substring(iEnd
+ endTag.Length);
}
else
result[0] = strSource;
}
else
result[1] = strSource;
return result;
}
}
}

View File

@ -0,0 +1,82 @@
using System.Web;
using JSIStudios.SimpleRESTServices.Core;
namespace JSIStudios.SimpleRESTServices.Server
{
public class HtmlTagReplacerAndCleaner : ITextCleaner
{
public static string Name = "ReplaceCleanHtml";
public string Clean(string source)
{
if (string.IsNullOrEmpty(source))
{
return null;
}
source = source.Replace("</p>", "</p>\n\n");
source = source.Replace("<br>", "\n\n");
source = source.Replace("<br />", "\n\n");
source = source.Replace("<br/>", "\n\n");
source = source.Replace("<BR>", "\n\n");
source = source.Replace("<BR />", "\n\n");
source = source.Replace("<BR/>", "\n\n");
char[] array = new char[source.Length];
int arrayIndex = 0;
bool inside = false;
string[,] special = new string[,] { { "&rsquo;", "'" }
, { "&nbsp;", " " }
, { "&quot;", "\"" }
, { "&apos;", "'" }
, { "&lt;", "<" }
, { "&raquo;", ">>" }
, { "&#160;", " "}
, { "&#39;", "'"}
, { "&gt;", ">" }
};
// , { "&mdash;", System.Windows.Browser.HttpUtility.HtmlDecode("&mdash;")}
var sourceLen = source.Length;
for (int idx = 0; idx < sourceLen; idx++)
{
char let = source[idx];
if (let == '<')
{
inside = true;
continue;
}
if (let == '>')
{
inside = false;
continue;
}
if (!inside)
{
if (let == '&')
{
for (int cnt = 0; cnt < special.GetLength(0); cnt++)
{
var specialLen = special[cnt, 0].Length;
if ((idx + specialLen) > sourceLen)
{
continue;
}
if (source.Substring(idx, specialLen).Equals(special[cnt, 0]))
{
let = (char)special[cnt, 1][0];
idx += specialLen - 1;
break;
}
}
}
array[arrayIndex] = let;
arrayIndex++;
}
}
//return new string(array, 0, arrayIndex);
var s = new string(array, 0, arrayIndex);
return HttpUtility.HtmlDecode(s.Trim());
}
}
}

View File

@ -0,0 +1,35 @@
<?xml version="1.0"?>
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata>
<id>SimpleRESTServices</id>
<version>0.0.0</version>
<title>SimpleRESTServices</title>
<authors>Alan Quillin, Sam Harwell</authors>
<owners>Alan Quillin</owners>
<licenseUrl>https://github.com/JSIStudios/SimpleRestServices/wiki/License</licenseUrl>
<projectUrl>https://github.com/JSIStudios/SimpleRestServices</projectUrl>
<requireLicenseAcceptance>true</requireLicenseAcceptance>
<description>A simple set of client side and server side REST helpers</description>
<releaseNotes>https://github.com/JSIStudios/SimpleRestServices/releases/tag/v$version$</releaseNotes>
<copyright>Copyright © JSI Studios 2013</copyright>
<tags>REST REST_client</tags>
<dependencies>
<dependency id="Newtonsoft.Json" version="5.0.6" />
</dependencies>
</metadata>
<files>
<!-- Runtime libraries -->
<file src="bin\v3.5\$Configuration$\SimpleRESTServices.dll" target="lib\net35"/>
<file src="bin\v3.5\$Configuration$\SimpleRESTServices.pdb" target="lib\net35"/>
<file src="..\Documentation\Api\v3.5\SimpleRESTServices.xml" target="lib\net35"/>
<file src="bin\v4.0\$Configuration$\SimpleRESTServices.dll" target="lib\net40"/>
<file src="bin\v4.0\$Configuration$\SimpleRESTServices.pdb" target="lib\net40"/>
<file src="..\Documentation\Api\v4.0\SimpleRESTServices.xml" target="lib\net40"/>
<!-- Source code -->
<file src="**\*.cs" target="src" />
</files>
</package>

View File

@ -0,0 +1,98 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>8.0.30703</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{1960D862-8AD9-48BE-9290-B7E23EA03D5C}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>JSIStudios.SimpleRESTServices</RootNamespace>
<AssemblyName>SimpleRESTServices</AssemblyName>
<TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<BaseIntermediateOutputPath>obj\v3.5\</BaseIntermediateOutputPath>
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\v3.5\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE;NET35</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<DocumentationFile>bin\v3.5\Debug\SimpleRESTServices.xml</DocumentationFile>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\v3.5\Release\</OutputPath>
<DefineConstants>TRACE;NET35</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<DocumentationFile>bin\v3.5\Release\SimpleRESTServices.xml</DocumentationFile>
</PropertyGroup>
<PropertyGroup>
<AssemblyOriginatorKeyFile Condition="'$(KeyConfiguration)' == 'Final'">..\..\build\keys\simplerestservices.snk</AssemblyOriginatorKeyFile>
<AssemblyOriginatorKeyFile Condition="'$(KeyConfiguration)' != 'Final'">..\..\build\keys\simplerestservices.dev.snk</AssemblyOriginatorKeyFile>
<SignAssembly>true</SignAssembly>
</PropertyGroup>
<ItemGroup>
<Reference Include="Newtonsoft.Json, Version=4.5.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\Newtonsoft.Json.5.0.6\lib\net35\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.ServiceModel" />
<Reference Include="System.ServiceModel.Web" />
<Reference Include="System.Web" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Client\HttpHeader.cs" />
<Compile Include="Client\HttpMethod.cs" />
<Compile Include="Client\IStringSerializer.cs" />
<Compile Include="Client\Json\JsonStringSerializer.cs" />
<Compile Include="Client\ResponseOfT.cs" />
<Compile Include="Client\RestServiceBase.cs" />
<Compile Include="Core\IRestService.cs" />
<Compile Include="Core\IUrlBuilder.cs" />
<Compile Include="Core\IRequestLogger.cs" />
<Compile Include="Client\Json\JsonRequestSettings.cs" />
<Compile Include="Client\Json\JsonRestService.cs" />
<Compile Include="Client\UrlBuilder.cs" />
<Compile Include="Client\RequestSettings.cs" />
<Compile Include="Client\Response.cs" />
<Compile Include="Client\WebResponseRetryLogic.cs" />
<Compile Include="Core\IRetryLogic.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<None Include="..\..\build\keys\simplerestservices.dev.snk">
<Link>simplerestservices.dev.snk</Link>
</None>
<None Include="..\..\build\keys\simplerestservices.snk">
<Link>simplerestservices.snk</Link>
</None>
<None Include="packages.SimpleRestServices.v3.5.config" />
<None Include="SimpleRestServices.nuspec" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<PropertyGroup>
<PostBuildEvent>
</PostBuildEvent>
</PropertyGroup>
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

View File

@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Description>A simple set of client side and server side REST helpers</Description>
<AssemblyTitle>SimpleRESTServices</AssemblyTitle>
<Authors>Alan Quillin, Sam Harwell</Authors>
<TargetFramework>net5.0</TargetFramework>
<PreserveCompilationContext>false</PreserveCompilationContext>
<AssemblyName>SimpleRESTServices</AssemblyName>
<PackageId>SimpleRESTServicesNET50</PackageId>
<PackageTags>REST, REST_client</PackageTags>
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<Version>1.3.0.2</Version>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Newtonsoft.Json" version="5.0.6" targetFramework="net35" />
</packages>

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Newtonsoft.Json" version="5.0.6" targetFramework="net40" />
</packages>

View File

@ -0,0 +1,36 @@
using System;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using JSIStudios.SimpleRESTServices.Client;
using JSIStudios.SimpleRESTServices.Client.Json;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace JSIStudios.SimpleRestServices.Testing.UnitTests.Client.Json
{
[TestClass]
public class JsonStringSerializerTests
{
[TestMethod]
public void DeserializeNullStringToNull()
{
IStringSerializer serializer = new JsonStringSerializer();
Assert.IsNull(serializer.Deserialize<object>(null));
}
[TestMethod]
public void DeserializeEmptyStringToNull()
{
IStringSerializer serializer = new JsonStringSerializer();
Assert.IsNull(serializer.Deserialize<object>(string.Empty));
}
[TestMethod]
public void SerializeNullObjectToNullString()
{
IStringSerializer serializer = new JsonStringSerializer();
Assert.IsNull(serializer.Serialize<object>(null));
}
}
}

View File

@ -0,0 +1,67 @@
using System;
using System.Collections.Generic;
using JSIStudios.SimpleRESTServices.Client;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace JSIStudios.SimpleRestServices.Testing.UnitTests.Client
{
[TestClass]
public class UrlBuilderTests
{
[TestMethod]
public void Should_Add_Query_String_Params_To_Existing_Url()
{
var paramList = new Dictionary<string, string> {{"key1", "value1"}, {"key2", "value2"}};
var url = "http://www.mytestsite.com";
var expectedUrl = "http://www.mytestsite.com?key1=value1&key2=value2";
var urlBuilder = new UrlBuilder();
var newUri = urlBuilder.Build(url, paramList);
Assert.AreEqual(expectedUrl, newUri);
}
[TestMethod]
public void Should_Add_Query_String_Params_To_Existing_Uri()
{
var paramList = new Dictionary<string, string> { { "key1", "value1" }, { "key2", "value2" } };
var uri = new Uri("http://www.mytestsite.com");
var expectedUrl = uri.AbsoluteUri + "?key1=value1&key2=value2";
var urlBuilder = new UrlBuilder();
var newUri = urlBuilder.Build(uri, paramList);
Assert.AreEqual(expectedUrl, newUri.AbsoluteUri);
}
[TestMethod]
public void Should_Append_Query_String_Params_To_Existing_Url()
{
var paramList = new Dictionary<string, string> { { "key1", "value1" }, { "key2", "value2" } };
var url = "http://www.mytestsite.com?key0=value0";
var expectedUrl = "http://www.mytestsite.com?key0=value0&key1=value1&key2=value2";
var urlBuilder = new UrlBuilder();
var newUri = urlBuilder.Build(url, paramList);
Assert.AreEqual(expectedUrl, newUri);
}
[TestMethod]
public void Should_Append_Query_String_Params_To_Existing_Uri()
{
var paramList = new Dictionary<string, string> { { "key1", "value1" }, { "key2", "value2" } };
var uri = new Uri("http://www.mytestsite.com?key0=value0");
var expectedUrl = uri.AbsoluteUri + "&key1=value1&key2=value2";
var urlBuilder = new UrlBuilder();
var newUri = urlBuilder.Build(uri, paramList);
Assert.AreEqual(expectedUrl, newUri.AbsoluteUri);
}
}
}

View File

@ -0,0 +1,35 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("UnitTests")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("UnitTests")]
[assembly: AssemblyCopyright("Copyright © 2012")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("fd0b0dad-9227-439b-a525-1a83414e3cbf")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View File

@ -0,0 +1,84 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>
</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{BB206928-0F2E-46A1-B438-D3ED9AF72BA1}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>JSIStudios.SimpleRestServices.Testing.UnitTests</RootNamespace>
<AssemblyName>UnitTests</AssemblyName>
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\..\</SolutionDir>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup>
<AssemblyOriginatorKeyFile>..\..\..\build\keys\TestingKey.snk</AssemblyOriginatorKeyFile>
<SignAssembly>true</SignAssembly>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.VisualStudio.QualityTools.UnitTestFramework, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" />
<Reference Include="Newtonsoft.Json, Version=4.5.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\packages\Newtonsoft.Json.5.0.6\lib\net40\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core">
<RequiredTargetFramework>3.5</RequiredTargetFramework>
</Reference>
</ItemGroup>
<ItemGroup>
<CodeAnalysisDependentAssemblyPaths Condition=" '$(VS100COMNTOOLS)' != '' " Include="$(VS100COMNTOOLS)..\IDE\PrivateAssemblies">
<Visible>False</Visible>
</CodeAnalysisDependentAssemblyPaths>
</ItemGroup>
<ItemGroup>
<Compile Include="Client\Json\JsonStringSerializerTests.cs" />
<Compile Include="Client\UrlBuilderTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<Folder Include="Server\" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\SimpleRestServices\SimpleRestServices.v4.0.csproj">
<Project>{4A44EB7C-06D5-43F0-B660-F684CBAAC99F}</Project>
<Name>SimpleRestServices.v4.0</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<None Include="..\..\..\build\keys\TestingKey.snk">
<Link>TestingKey.snk</Link>
</None>
<None Include="packages.config" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Newtonsoft.Json" version="5.0.6" targetFramework="net40" />
</packages>

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<TestSettings name="Trace and Test Impact" id="e3b04119-3262-4296-85ca-4b0acf867a8b" xmlns="http://microsoft.com/schemas/VisualStudio/TeamTest/2010">
<Description>These are test settings for Trace and Test Impact.</Description>
<Execution>
<TestTypeSpecific />
<AgentRule name="Execution Agents">
<DataCollectors>
<DataCollector uri="datacollector://microsoft/SystemInfo/1.0" assemblyQualifiedName="Microsoft.VisualStudio.TestTools.DataCollection.SystemInfo.SystemInfoDataCollector, Microsoft.VisualStudio.TestTools.DataCollection.SystemInfo, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" friendlyName="System Information">
</DataCollector>
<DataCollector uri="datacollector://microsoft/ActionLog/1.0" assemblyQualifiedName="Microsoft.VisualStudio.TestTools.ManualTest.ActionLog.ActionLogPlugin, Microsoft.VisualStudio.TestTools.ManualTest.ActionLog, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" friendlyName="Actions">
</DataCollector>
<DataCollector uri="datacollector://microsoft/HttpProxy/1.0" assemblyQualifiedName="Microsoft.VisualStudio.TraceCollector.HttpProxyCollector, Microsoft.VisualStudio.TraceCollector, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" friendlyName="ASP.NET Client Proxy for IntelliTrace and Test Impact">
</DataCollector>
<DataCollector uri="datacollector://microsoft/TestImpact/1.0" assemblyQualifiedName="Microsoft.VisualStudio.TraceCollector.TestImpactDataCollector, Microsoft.VisualStudio.TraceCollector, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" friendlyName="Test Impact">
</DataCollector>
<DataCollector uri="datacollector://microsoft/TraceDebugger/1.0" assemblyQualifiedName="Microsoft.VisualStudio.TraceCollector.TraceDebuggerDataCollector, Microsoft.VisualStudio.TraceCollector, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" friendlyName="IntelliTrace">
</DataCollector>
</DataCollectors>
</AgentRule>
</Execution>
</TestSettings>