diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..940794e --- /dev/null +++ b/.gitignore @@ -0,0 +1,288 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ +**/Properties/launchSettings.json + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# TODO: Comment the next line if you want to checkin your web deploy settings +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Typescript v1 declaration files +typings/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush +.cr/ + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs diff --git a/src/DebugApp/App.config b/src/DebugApp/App.config new file mode 100644 index 0000000..88fa402 --- /dev/null +++ b/src/DebugApp/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/DebugApp/DebugApp.csproj b/src/DebugApp/DebugApp.csproj new file mode 100644 index 0000000..61a4e9f --- /dev/null +++ b/src/DebugApp/DebugApp.csproj @@ -0,0 +1,59 @@ + + + + + Debug + AnyCPU + {F2D82A82-100B-4594-B50F-7C7C815C4370} + Exe + DebugApp + DebugApp + v4.5.2 + 512 + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll + + + ..\packages\RestSharp.105.2.3\lib\net452\RestSharp.dll + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/DebugApp/Program.cs b/src/DebugApp/Program.cs new file mode 100644 index 0000000..69c7c5d --- /dev/null +++ b/src/DebugApp/Program.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace DebugApp +{ + class Program + { + static void Main(string[] args) + { + } + } +} diff --git a/src/DebugApp/Properties/AssemblyInfo.cs b/src/DebugApp/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..41dfbc1 --- /dev/null +++ b/src/DebugApp/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +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("DebugApp")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("DebugApp")] +[assembly: AssemblyCopyright("Copyright © 2017")] +[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("f2d82a82-100b-4594-b50f-7c7c815c4370")] + +// 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.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/src/DebugApp/packages.config b/src/DebugApp/packages.config new file mode 100644 index 0000000..d186dc1 --- /dev/null +++ b/src/DebugApp/packages.config @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/DynaWeb.All.sln b/src/DynaWeb.All.sln new file mode 100644 index 0000000..ae074cc --- /dev/null +++ b/src/DynaWeb.All.sln @@ -0,0 +1,34 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.26430.16 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DynaWeb", "DynaWeb\DynaWeb.csproj", "{CE19C882-1AAC-434C-99AF-4A285DA053BA}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DebugApp", "DebugApp\DebugApp.csproj", "{F2D82A82-100B-4594-B50F-7C7C815C4370}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DynaWeb.Core.Tests", "..\test\DynaWeb.Core.Tests\DynaWeb.Core.Tests.csproj", "{50A649C6-FB32-4C6D-A7B6-6EB99DE8547D}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {CE19C882-1AAC-434C-99AF-4A285DA053BA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CE19C882-1AAC-434C-99AF-4A285DA053BA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CE19C882-1AAC-434C-99AF-4A285DA053BA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CE19C882-1AAC-434C-99AF-4A285DA053BA}.Release|Any CPU.Build.0 = Release|Any CPU + {F2D82A82-100B-4594-B50F-7C7C815C4370}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F2D82A82-100B-4594-B50F-7C7C815C4370}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F2D82A82-100B-4594-B50F-7C7C815C4370}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F2D82A82-100B-4594-B50F-7C7C815C4370}.Release|Any CPU.Build.0 = Release|Any CPU + {50A649C6-FB32-4C6D-A7B6-6EB99DE8547D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {50A649C6-FB32-4C6D-A7B6-6EB99DE8547D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {50A649C6-FB32-4C6D-A7B6-6EB99DE8547D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {50A649C6-FB32-4C6D-A7B6-6EB99DE8547D}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/src/DynaWeb/Classes/Execution.cs b/src/DynaWeb/Classes/Execution.cs new file mode 100644 index 0000000..fbb9dd7 --- /dev/null +++ b/src/DynaWeb/Classes/Execution.cs @@ -0,0 +1,212 @@ +using RestSharp; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Text; +using System.Threading.Tasks; + +namespace DynaWeb +{ + /// + /// Provides support for executing WebRequests + /// + public static class Execute + { + #region internal methods + private static WebResponse ClientRequestMethod(WebClient webClient, WebRequest webRequest) + { + if (webRequest == null) throw new ArgumentNullException(DynaWeb.Properties.Resources.WebClientRequestNullMessage); + // build a client & request to execute + WebClient client; + WebRequest request = webRequest; + + // check if client is null : this will be the case when executing a WebRequest directly + if (webClient == null) + { + // in that case, an empty WebClient will be constructed with the WebRequest URL as its baseUrl. + // the request Resource also needs to be reset, otherwise the URL would be concatenating to itself. + client = WebClient.ByUrl(webRequest.URL); + request.Resource = ""; + } + else + { + client = webClient; + } + client.UserAgent = "DynamoDS"; + + // validate the Uri before attempting to execute the request + try + { + var uri = WebClient.BuildUri(client, request); + if (string.IsNullOrEmpty(uri) || DynaWeb.Helpers.CheckURI(Helpers.ParseUriFromString(uri)) != true) + { + //TODO : error handling here is limited, needs checking and expanding. + throw new InvalidOperationException("Malformed URL."); + } + } + catch (Exception e) + { + throw new InvalidOperationException( + DynaWeb.Properties.Resources.WebClientBuildUrlFailed + + Environment.NewLine + + "Error returned was :" + Environment.NewLine + + e.Message); + } + + // enforce security protocols if needed + if (webRequest.ForceSecurityProtocol) + { + ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; + ServicePointManager.DefaultConnectionLimit *= 10; + } + + // Execute using the wrapped client and wrapped request objects. + var startTime = DateTime.Now; + var responseFromServer = client.restClient.Execute(webRequest.restRequest); + var endTime = DateTime.Now; + + // the server response needs to be handled based on status and any errors raised in UI + switch (responseFromServer.ResponseStatus) + { + case ResponseStatus.None: + throw new InvalidOperationException(DynaWeb.Properties.Resources.WebResponseNetworkErrorMessage); + case ResponseStatus.Error: + throw new InvalidOperationException(DynaWeb.Properties.Resources.WebResponseNetworkErrorMessage); + case ResponseStatus.TimedOut: + throw new InvalidOperationException(DynaWeb.Properties.Resources.WebRequestTimedOutMessage); + case ResponseStatus.Aborted: + throw new InvalidOperationException(DynaWeb.Properties.Resources.WebResponseAbortedMessage); + default: + break; + } + + // update the request properties with response data + webRequest.response = new WebResponse(responseFromServer); + webRequest.response.Time = endTime - startTime; + + return webRequest.response; + } + #endregion + + #region extension methods + + /// + /// Execute the given request, on a client if one is supplied. + /// + /// The client that will execute the request. + /// Note : the WebClient can be NULL when executing a WebRequest directly. + /// The request to be executed. + /// The string that represents the http method. + /// Valid input : GET, DELETE, HEAD, OPTIONS, POST, PUT, MERGE. + /// The WebResponse from the server. + public static WebResponse ByClientRequestMethod(WebClient webClient, WebRequest webRequest, string method = null) + { + // initialise method with GET as default + webRequest.restRequest.Method = Method.GET; + // then try to parse value given (if any). If this fails, method is not changed. + if (!string.IsNullOrEmpty(method) && Enum.TryParse(method, true, out Method reqMethod)) + webRequest.restRequest.Method = reqMethod; + + return ClientRequestMethod(webClient, webRequest); + } + + /// + /// Execute the given request, on a client if one is supplied, using the standard http GET method. + /// + /// The request to be executed. + /// (optional) The client that will execute the request. + /// The response from the server. + public static WebResponse GET(WebRequest webRequest, WebClient webClient = null) + { + webRequest.restRequest.Method = Method.GET; + return ClientRequestMethod(webClient, webRequest); + } + + /// + /// Execute the given request, on a client if one is supplied, using the standard http POST method. + /// + /// The request to be executed. + /// (optional) The client that will execute the request. + /// The response from the server. + public static WebResponse POST(WebRequest webRequest, WebClient webClient = null) + { + webRequest.restRequest.Method = Method.POST; + return ClientRequestMethod(webClient, webRequest); + } + + /// + /// Execute the given request, on a client if one is supplied, using the standard http PUT method. + /// + /// The request to be executed. + /// (optional) The client that will execute the request. + /// The response from the server. + public static WebResponse PUT(WebRequest webRequest, WebClient webClient = null) + { + webRequest.restRequest.Method = Method.PUT; + return ClientRequestMethod(webClient, webRequest); + } + + /// + /// Execute the given request, on a client if one is supplied, using the standard http DELETE method. + /// + /// The request to be executed. + /// (optional) The client that will execute the request. + /// The response from the server. + public static WebResponse DELETE(WebRequest webRequest, WebClient webClient = null) + { + webRequest.restRequest.Method = Method.DELETE; + return ClientRequestMethod(webClient, webRequest); + } + + /// + /// Execute the given request, on a client if one is supplied, using the standard http HEAD method. + /// + /// The request to be executed. + /// (optional) The client that will execute the request. + /// The response from the server. + public static WebResponse HEAD(WebRequest webRequest, WebClient webClient = null) + { + webRequest.restRequest.Method = Method.HEAD; + return ClientRequestMethod(webClient, webRequest); + } + + /// + /// Execute the given request, on a client if one is supplied, using the standard http MERGE method. + /// + /// The request to be executed. + /// (optional) The client that will execute the request. + /// The response from the server. + public static WebResponse MERGE(WebRequest webRequest, WebClient webClient = null) + { + webRequest.restRequest.Method = Method.MERGE; + return ClientRequestMethod(webClient, webRequest); + } + + /// + /// Execute the given request, on a client if one is supplied, using the standard http OPTIONS method. + /// + /// The request to be executed. + /// (optional) The client that will execute the request. + /// The response from the server. + public static WebResponse OPTIONS(WebRequest webRequest, WebClient webClient = null) + { + webRequest.restRequest.Method = Method.OPTIONS; + return ClientRequestMethod(webClient, webRequest); + } + + /// + /// Execute the given request, on a client if one is supplied, using the standard http PATCH method. + /// + /// The request to be executed. + /// (optional) The client that will execute the request. + /// The response from the server. + public static WebResponse PATCH(WebRequest webRequest, WebClient webClient = null) + { + webRequest.restRequest.Method = Method.PATCH; + return ClientRequestMethod(webClient, webRequest); + } + + #endregion + } +} diff --git a/src/DynaWeb/Classes/WebClient.cs b/src/DynaWeb/Classes/WebClient.cs new file mode 100644 index 0000000..c5ea67e --- /dev/null +++ b/src/DynaWeb/Classes/WebClient.cs @@ -0,0 +1,252 @@ +using Autodesk.DesignScript.Runtime; +using RestSharp; +using System; + +namespace DynaWeb +{ + /// + /// A web client is used to translate request objects into HTTP requests and process the server response. + /// The web client also represents a uniquely configured connection to a server or service. + /// + public class WebClient + { + + #region private members + /// + /// This is the wrapped RestSharp RestClient. + /// + internal RestClient restClient; + + /// + /// This auth token is used to authenticate requests made by the client. + /// Use it as the private store for OAuth tokens for example. + /// + private string authToken; + + /// + /// This auth code is used during OAuth authentication flows. + /// + private string authCode; + + /// + /// This string is used to verify responses from the server during authentication flows. + /// + private string authVerification; + #endregion + + #region public client settings + + /// + /// This is the base URL for all future requests made by its client. + /// This URL is combined with the request resource (ex: DynamoDS/Dynamo/issues) to construct the final URL for request. + /// (ex: https://github.com/DynamoDS/Dynamo/issues) + /// Should include scheme (ex: http://) and domain (ex: www.dynamobim.org) without trailing slash (/). + /// + public Uri BaseUrl { get => restClient.BaseUrl; set => restClient.BaseUrl = value; } + + /// + /// Optional UserAgent to use for requests made by this client instance. (ex: Dynamo1.3) + /// Used by the server to record where the request is coming from. + /// + public string UserAgent { get => restClient.UserAgent; set => restClient.UserAgent = value; } + + /// + /// Timeout in milliseconds to use for requests made by this client instance + /// + public int Timeout { get => restClient.Timeout; set => restClient.Timeout = value; } + + /// + /// Determine whether or not requests that result in HTTP status codes of 3xx should follow returned redirect. Defaults to true. + /// + public bool FollowRedirects + { + get => restClient.FollowRedirects; + set + { + restClient.FollowRedirects = value; + if (restClient.FollowRedirects == true && MaxRedirects == null) + MaxRedirects = 1; + } + } + + /// + /// Maximum number of redirects to follow if FollowRedirects is true. Defaults to 1 if not set. + /// + public int? MaxRedirects + { + get + { + if (FollowRedirects == true) return restClient.MaxRedirects; + else return null; + } + set => restClient.MaxRedirects = value; + } + + /// + /// Use this to override which node in the JSON response is used as the root/starting point for deserialisation. + /// + public string JsonTokenOverride { get; set; } + + #endregion + + #region constructors + + /// + /// Build a new WebClient using the specified URL as its base. + /// A web client is used to translate request objects into HTTP requests and process the server response. + /// The web client also represents a uniquely configured connection to a server or service. + /// + /// The URL to use for all future requests made by this client. + /// Should include scheme (ex: http://) and domain (ex: www.dynamobim.org) without trailing slash (/). + /// + public static WebClient ByUrl(string baseUrl) + { + return new WebClient(baseUrl, ""); + } + + /// + /// Build a new WebClient using the specified URL as its base. + /// A web client is used to translate request objects into HTTP requests and process the server response. + /// The web client also represents a uniquely configured connection to a server or service. + /// + /// The URL to use for all future requests made by this client. + /// Should include scheme (ex: http://) and domain (ex: www.dynamobim.org) without trailing slash (/). + /// + /// The auth token is used to authenticate requests made by the client. + /// Use it as the private store for OAuth tokens for example. + /// Once the client is created, this cannot be changed. + public static WebClient ByUrlToken(string baseUrl, string token) + { + if (string.IsNullOrEmpty(token)) throw new ArgumentNullException(DynaWeb.Properties.Resources.WebClientTokenNullMessage); + + return new WebClient(baseUrl, token); + } + + private WebClient(string baseUrl, string token="") + { + if (string.IsNullOrEmpty(baseUrl)) throw new ArgumentNullException(DynaWeb.Properties.Resources.WebClientUrlNullMessage); + + this.restClient = new RestClient(baseUrl); + this.authToken = token; + this.UserAgent = "DynamoDS"; + } + + #endregion + + #region execution + + /// + /// Executes a WebRequest in the context of the client and returns the response from the server. + /// + /// The WebClient to use for execution of request. + /// The web WebRequest to execute. + /// The response from the server as a WebResponse object. + [CanUpdatePeriodically(true)] + public static WebResponse Execute(WebClient client, WebRequest request) + { + if (client==null) throw new ArgumentNullException(DynaWeb.Properties.Resources.WebClientNullMessage); + if (request == null) throw new ArgumentNullException(DynaWeb.Properties.Resources.WebRequestNullMessage); + + request.response = DynaWeb.Execute.ByClientRequestMethod(client, request, request.Method.ToString()); + return request.response; + } + + #endregion + + #region public static methods + + /// + /// Assembles the URL to call based on parameters, method and resource. + /// Not needed to run the request, but useful for debugging purposes. + /// + /// The WebClient to update. + /// The request to execute + /// A string representation of the assembly Uri + public static string BuildUri(WebClient client, WebRequest request) + { + if (request == null) throw new ArgumentNullException(DynaWeb.Properties.Resources.WebClientRequestNullMessage); + + return client.restClient.BuildUri(request.restRequest).ToString(); + } + + /// + /// Set the base URL for this client. + /// + /// The WebClient to update. + /// The value to set BaseUrl to, has to be a valid URL. + /// The WebClient supplied with an updated BaseUrl property. + public WebClient SetBaseURL(string url) + { + if (string.IsNullOrEmpty(url)) throw new ArgumentNullException(DynaWeb.Properties.Resources.WebClientUrlNullMessage); + if (!Helpers.CheckURI(Helpers.ParseUriFromString(url))) throw new ArgumentNullException(DynaWeb.Properties.Resources.WebUrlInvalidMessage); + this.BaseUrl = Helpers.ParseUriFromString(url); + return this; + } + + /// + /// Set the user agent communicated with requests this client sends. + /// + /// The WebClient to update. + /// The value to set the UserAgent to. + /// The WebClient supplied with the an UserAgent property. + public WebClient SetUserAgent(string userAgent) + { + if (string.IsNullOrEmpty(userAgent)) throw new ArgumentNullException(DynaWeb.Properties.Resources.WebClientUserAgentNullMessage); + this.UserAgent = userAgent; + return this; + } + + /// + /// Set the timeout in milliseconds to use for requests made by this client instance + /// + /// The WebClient to update. + /// The value to set timeout to, expressed in milliseconds. + /// The WebClient supplied with an updated Timeout property. + public WebClient SetTimeout(int timeout) + { + if (timeout <= 0) throw new ArgumentNullException(DynaWeb.Properties.Resources.WebClientTimeoutInvalidMessage); + this.Timeout = timeout; + return this; + } + + /// + /// Sets the FollowRedirects setting of the client. This controls whether or not requests that result in HTTP status codes of 3xx should follow returned redirect. Default is true. + /// + /// The WebClient to update. + /// True to follow redirects, false to end request. + /// The WebClient supplied with an updated FollowRedirects property. + public WebClient SetFollowRedirects(bool followRedirects = true) + { + this.FollowRedirects = followRedirects; + return this; + } + + /// + /// Set the maximum number of redirects to follow if FollowRedirects is true. + /// + /// The WebClient to update. + /// The value to set maximum to, expressed as an integer. + /// The WebClient supplied with an updated MaxRedirects property. + public WebClient SetMaxRedirects(int maxRedirects) + { + if (maxRedirects <= 0) throw new ArgumentNullException(DynaWeb.Properties.Resources.WebClientTimeoutInvalidMessage); + this.MaxRedirects = maxRedirects; + return this; + } + + /// + /// Set the JsonTokenOverride that is used for deserialisation purposes. + /// + /// The WebClient to update. + /// The value to set JsonTokenOverride to. + /// The WebClient supplied with an updated JsonTokenOverride property. + public WebClient SetJsonTokenOverride(string jsonToken) + { + if (string.IsNullOrEmpty(jsonToken)) throw new ArgumentNullException(DynaWeb.Properties.Resources.WebClientTokenNullMessage); + this.JsonTokenOverride = jsonToken; + return this; + } + + #endregion + } +} diff --git a/src/DynaWeb/Classes/WebRequest.cs b/src/DynaWeb/Classes/WebRequest.cs new file mode 100644 index 0000000..99a38ef --- /dev/null +++ b/src/DynaWeb/Classes/WebRequest.cs @@ -0,0 +1,501 @@ +using Autodesk.DesignScript.Runtime; +using RestSharp; +using System; +using System.Collections.Generic; +using System.Net; +using System.Collections.Specialized; +using System.IO; + +namespace DynaWeb +{ + /// + /// A web request is the mechanism through which we can communicate with web servers, + /// requesting information from them or send data to them. + /// + public class WebRequest + { + #region constants + + /// + /// The default timeout to wait for a request to return, expressed in miliseconds + /// + private const int defaultRequestTimeout = 1500; + + /// + /// The default number of times to retry a failed request + /// + private const int defaultRequestAttempts = 3; + + /// + /// The default security protocol to default to. + /// The protocol is required for interaction with HTTPS endpoints, irrespective of using System.Net or RestSharp libraries. + /// + private const SecurityProtocolType defaultSecurityProtocol = SecurityProtocolType.Tls12; + + /// + /// The default URL to use when constructing a WebRequest by endpoint only. + /// This then gets disregarded by the WebClient during execution. + /// + private const string defaultUrl = "http://www.dynamobim.org"; + + #endregion + + #region private/internal properties + + /// + /// The encapsulated Restsharp web request + /// + internal RestRequest restRequest = new RestRequest(); + + /// + /// The encapsulated response from the server + /// + internal WebResponse response = new WebResponse(new RestResponse()); + + internal Uri url; + + private StringDictionary headers = new StringDictionary(); + private Dictionary parameters = new Dictionary(); + + #endregion + + #region public properties + + /// + /// The response the server returned on the last execution of the request. + /// + public WebResponse Response => this.response; + + /// + /// The URL for the request. + /// This is ignored when the request is executed by a WebClient node, use the WebRequest "resource" in that case. + /// + public string URL + { + get => url.ToString(); + set { url = Helpers.ParseUriFromString(value.ToString()); } + } + + /// + /// Set this property to true to force requests to go through a security protocol (TLS1.2). + /// This is required when interacting with servers and APIs that require use of HTTPS as a protocol rather than HTTP. + /// + public bool ForceSecurityProtocol = false; + + #endregion + + #region IRestRequest interface fields + + /// + /// Container of all HTTP parameters to be passed with the request. + /// See for explanation of the types of parameters that can be passed + /// + public List Parameters => restRequest.Parameters; + + /// + /// Container of all the files to be uploaded with the request. + /// + public List Files => restRequest.Files; + + /// + /// Determines what HTTP method to use for this request. + /// Supported methods: GET, POST, PUT, DELETE, HEAD, OPTIONS + /// Default is GET + /// + public Method Method + { + get => restRequest.Method; + set => restRequest.Method = value; + } + + /// + /// The Resource URL to make the request against, should not include the scheme or domain. + /// Ignored when the WebRequest is not executed through a WebClient. + /// Do not include leading slash. Combined with web client BaseUrl to assemble final URL: + /// {BaseUrl}/{Resource} (BaseUrl is scheme + domain, e.g. http://example.com) + /// + public string Resource + { + get => restRequest.Resource; + set => restRequest.Resource = value; + } + + /// + /// Used by the default deserializers to determine where to start deserializing from. + /// Can be used to skip container or root elements that do not have corresponding deserialzation targets. + /// Example : most APIs return values wrapped in a root "data" element. + /// + public string RootElement + { + get => restRequest.RootElement; + set => restRequest.RootElement = value; + } + + /// + /// Timeout in milliseconds to be used for the request. + /// This timeout value overrides a timeout set on the Web Client. + /// + public int Timeout + { + get => restRequest.Timeout; + set => restRequest.Timeout = value; + } + + /// + /// The number of milliseconds before the writing or reading times out. + /// This timeout value overrides a timeout set on a Web Client. + /// + public int ReadWriteTimeout + { + get => restRequest.ReadWriteTimeout; + set => restRequest.ReadWriteTimeout = value; + } + + /// + /// How many attempts were made to send this Request? + /// This Number is incremented each time the web client sends the request. + /// Useful when using Asynchronous Execution with Callbacks + /// + public int Attempts => restRequest.Attempts; + + /// + /// Serializer to use. Can be JSON or XML, defaults to XML. + /// + public DataFormat RequestFormat + { + get => restRequest.RequestFormat; + set => restRequest.RequestFormat = value; + } + + #endregion + + #region constructor methods + + /// + /// Private constructor : Build a simple GET web request to the specified URL + /// + /// The URL to send the request to. + /// The endpoint, or resource, used in conjunction with a WebClient base URL. + /// The request object, ready for execution. + private WebRequest(string url, string resource) + { + // handle the case where only endpoint is needed, + // but a valid URL is still required for the RestSharp request constructor + if (string.IsNullOrEmpty(url) && !string.IsNullOrEmpty(resource)) + URL = defaultUrl; + else URL = url; + + restRequest = new RestRequest(this.URL, Method.GET); + restRequest.Resource = resource; + } + + /// + /// Build a simple GET web request to the specified URL + /// + /// The URL to send the request to. + /// The request object, ready for execution. + public static WebRequest ByUrl(string url) + { + if (string.IsNullOrEmpty(url)) throw new ArgumentNullException(DynaWeb.Properties.Resources.WebUrlNullMessage); + return new WebRequest(url, null); + } + + /// + /// Build a simple GET web request to the specified URL + /// + /// The resource (or endpoint) to use for the request. + /// This will be used in conjunction with a WebClient base URL to form the full request URL. + /// ex : "users". + /// The request object, ready for execution. + public static WebRequest ByEndpoint(string endpoint) + { + if (string.IsNullOrEmpty(endpoint)) throw new ArgumentNullException(DynaWeb.Properties.Resources.WebRequestEndpointNullMessage); + return new WebRequest(null, endpoint); + } + + #endregion + + #region Execution + + /// + /// Executes a WebRequest and returns the response from the server. + /// + /// The web request to execute. + /// The response from the server as a WebResponse object. + [CanUpdatePeriodically(true)] + public static WebResponse Execute(WebRequest request) + { + request.response = DynaWeb.Execute.ByClientRequestMethod(null, request, request.Method.ToString()); + return request.response; + } + + #endregion + + #region extension methods + + /// + /// Sets the HTTP method to use for the request. + /// Valid input : GET, DELETE, HEAD, OPTIONS, POST, PUT, MERGE + /// Note : input is not case-sensitive. + /// + /// The request to update. + /// The string that represents the http method. + /// The WebRequest updated with set method if input was valid, the unchanged WebRequest otherwise. + public WebRequest SetMethod(string method) + { + if (Enum.TryParse(method, true, out Method reqMethod)) + this.restRequest.Method = reqMethod; + else throw new InvalidDataException("Could not parse the method!"); + + return this; + } + + /// + /// Sets the default serialiser to use with this request. + /// + /// The request to update + /// The serialiser to use as case-insensitive string. + /// Valid inputs : JSON, XML + /// The updated request if supplied format was valid, the unchanged request if not. + public WebRequest SetRequestFormat(string format) + { + if (Enum.TryParse(format, true, out DataFormat dataFormat)) + this.restRequest.RequestFormat = dataFormat; + return this; + } + + /// + /// Sets the URL of the request. + /// + /// The request to update + /// The URL to set for the request. + /// The request with an updated URL. + public WebRequest SetUrl(string url) + { + this.URL = url; + return this; + } + + /// + /// Sets the resource of the request. Ignored when not executed through a WebClient. + /// This is combined with a WebClient base URL to form a complete request URL. + /// + /// The request to update + /// The resource to set for the request. + /// The request with an updated URL. + public WebRequest SetResource(string resource) + { + this.Resource = resource; + return this; + } + + /// + /// Sets the value of the ForceSecurityProtocol property. + /// Use this property to foce use of TLS1.2, required when interacting over HTTPS. + /// + /// The request to update + /// True or False + /// The request + public WebRequest SetSecurityProtocol(bool forceSecurity) + { + this.ForceSecurityProtocol = forceSecurity; + return this; + } + + /// + /// Adds a file to the Files collection to be included with a POST or PUT request (other methods do not support file uploads). + /// + /// The request to update + /// The parameter name to use in the request + /// Full path to file to upload + /// The MIME type of the file to upload + /// This request + public WebRequest AddFile(string name, string path, string contentType = null) + { + if (this.restRequest.Method != Method.POST && this.restRequest.Method != Method.PUT) + throw new InvalidOperationException("Can only add a file to a POST or PUT request."); + + this.restRequest.AddFile(name, path, contentType); + return this; + } + + /// + /// Adds the raw bytes of a file to the body of the request. Useful for situations when multipart data is not supported by the server. + /// + /// The name of the parameter, usually the name of the file. + /// The file name. + /// The full path to the file to be uploaded. + /// The request updated. + public WebRequest AddFileAsBytes(string name, string filename, string filepath) + { + try + { + var bytes = File.ReadAllBytes(filepath); + this.restRequest.AddParameter("application/octet-stream", bytes, ParameterType.RequestBody); + } + catch (Exception) + { + throw new InvalidOperationException(DynaWeb.Properties.Resources.WebIOFileNotRead); + } + + return this; + } + + + /// + /// Serializes obj to data format specified by RequestFormat and adds it to the request body. + /// The default format is XML. Change RequestFormat if you wish to use a different serialization format. + /// + /// The request to update + /// The object to serialize + /// This request + public WebRequest AddBody(object obj) + { + if (this.restRequest.Method == Method.GET) throw new InvalidOperationException("Cannot add a body parameter to a GET request"); + + this.restRequest.AddBody(obj); + return this; + } + + /// + /// Serializes obj to JSON format and adds it to the request body. + /// + /// The request to update + /// The object to serialize + /// This request + public WebRequest AddJsonBody(object obj) + { + if (this.restRequest.Method == Method.GET) throw new InvalidOperationException("Cannot add a body parameter to a GET request"); + + this.restRequest.AddJsonBody(obj); + return this; + } + + /// + /// Serializes obj to XML format and adds it to the request body. + /// + /// The request to update + /// The object to serialize + /// This request + public WebRequest AddXmlBody(object obj) + { + if (this.restRequest.Method == Method.GET) throw new InvalidOperationException("Cannot add a body parameter to a GET request"); + + this.restRequest.AddXmlBody(obj); + return this; + } + + /// + /// Calls AddParameter() for all public, readable properties of obj + /// + /// The request to update + /// The object with properties to add as parameters + /// This request + public WebRequest AddObject(object obj) + { + var hash = obj.GetHashCode().ToString(); + if (this.parameters.ContainsKey(hash)) return this; + + this.parameters.Add(hash, obj); + this.restRequest.AddObject(obj); + return this; + } + + /// + /// Adds a HTTP parameter to the request. + /// Uses QueryString for GET, DELETE, OPTIONS and HEAD, Encoded form for POST and PUT + /// + /// The request to update + /// The name of the parameter to add. + /// The value of the parameter to add. + /// The type of the parameter to add. + /// Valid inputs: Cookie, GetOrPost, HttpHeader, QueryString, RequestBody, UrlSegment + /// The request with the added parameter. + public WebRequest AddParameter(string name, object value, string parameterType) + { + if (string.IsNullOrEmpty(name)) + throw new ArgumentNullException("Name parameter cannot be null."); + if (value.Equals(null)) + throw new ArgumentNullException("Value parameter cannot be null."); + if (Enum.TryParse(parameterType, true, out ParameterType pType) == false) + throw new ArgumentException("Could not parse the supplied value into a valid Parameter Type."); + + try + { + this.parameters.Add(name, value); + // if the item was added, we should also add it to the wrapped RestRequest + this.restRequest.AddParameter(name, value, pType); + } + catch (Exception e) + { + // the addition silently fails + // TODO : add warning bubble on node without throwing Exception + // as that would stop downstream nodes from executing + } + return this; + } + + /// + /// Shortcut to AddParameter(name, value, HttpHeader) + /// + /// The request to update + /// Name of the header to add + /// Value of the header to add + /// + public WebRequest AddHeader(string name, string value) + { + try + { + this.headers.Add(name, value); + // if the header was added, we should also add it to the wrapped RestRequest + this.restRequest.AddHeader(name, value); + } + catch (Exception e) + { + // the addition silently fails + // TODO : add warning bubble on node without throwing Exception + // as that would stop downstream nodes from executing + } + return this; + } + + /// + /// Shortcut to AddParameter(name, value, Cookie) + /// + /// The request to update + /// Name of the cookie to add + /// Value of the cookie to add + /// + public WebRequest AddCookie(string name, string value) + { + this.restRequest.AddCookie(name, value); + return this; + } + + /// + /// Shortcut to AddParameter(name, value, UrlSegment) + /// + /// The request to update + /// Name of the segment to add + /// Value of the segment to add + /// + public WebRequest AddUrlSegment(string name, string value) + { + return AddParameter(name, value, ParameterType.UrlSegment.ToString()); + } + + /// + /// Shortcut to AddParameter(name, value, QueryString) + /// + /// The request to update + /// Name of the parameter to add + /// Value of the parameter to add + /// + public WebRequest AddQueryParameter(string name, string value) + { + return AddParameter(name, value, ParameterType.QueryString.ToString()); + } + + #endregion + } +} diff --git a/src/DynaWeb/Classes/WebResponse.cs b/src/DynaWeb/Classes/WebResponse.cs new file mode 100644 index 0000000..10cdb86 --- /dev/null +++ b/src/DynaWeb/Classes/WebResponse.cs @@ -0,0 +1,92 @@ +using RestSharp; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Net; +using Autodesk.DesignScript.Runtime; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System.Linq; + +namespace DynaWeb +{ + /// + /// The response returned from a server after a WebRequest was executed + /// + public class WebResponse + { + #region private properties + private IRestResponse response; + #endregion + + #region public properties + + // properties relating to the content of the response + public string Content => this.response.Content; + public string ContentType => this.response.ContentType; + public long ContentLength => this.response.ContentLength; + public string ContentEncoding => this.response.ContentEncoding; + public byte[] RawBytes => this.response.RawBytes; + + // properties relating to the status of the response + public string StatusCode => this.response.StatusCode.ToString(); + public string StatusDescription => this.response.StatusDescription; + public string ResponseStatus => this.response.ResponseStatus.ToString(); + public string ErrorException => this.response.ErrorException.ToString(); + public string ErrorMessage => this.response.ErrorMessage; + public System.TimeSpan Time { get; internal set; } + + // meta properties that have information about the response itself + public Uri ResponseUri => this.response.ResponseUri; + public string Server => this.response.Server; + public List> Headers + { + get + { + var headersDict = this.response.Headers.ToDictionary(x => x.Name); + var headers = new List>(); + headers.Add(headersDict.Keys.ToList()); + headers.Add(headersDict.Values.Select(x => x.Value.ToString()).ToList()); + + return headers; + } + } + + public List> Cookies + { + get + { + var result = new List>(); + + foreach (var cookie in this.response.Cookies) + { + var values = new List(); + values.Add(cookie.Name); + values.Add(cookie.Value); + values.Add(cookie.TimeStamp.ToString()); + + result.Add(new List() { "Name", "Value", "TimeStamp" }); + result.Add(values); + } + + return result; + } + } + + #endregion + + #region constructor + /// + /// Represents data returned by the server, contains both meta-data about the response/server as well the content of the response itself. + /// + /// Extract the response after the execution of a WebRequest. + /// Connect the output of "Execute" nodes to this as input. + public WebResponse(IRestResponse res) + { + this.response = res; + } + #endregion + } +} diff --git a/src/DynaWeb/Config/keys.sample.xml b/src/DynaWeb/Config/keys.sample.xml new file mode 100644 index 0000000..fbd3555 --- /dev/null +++ b/src/DynaWeb/Config/keys.sample.xml @@ -0,0 +1,6 @@ + + + INSERT YOU API KEY HERE + rename this file to keys.xml + Bearer 0/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + \ No newline at end of file diff --git a/src/DynaWeb/Config/pkg.json b/src/DynaWeb/Config/pkg.json new file mode 100644 index 0000000..65b0712 --- /dev/null +++ b/src/DynaWeb/Config/pkg.json @@ -0,0 +1,26 @@ +{ + "license": "AGPL-3.0", + "file_hash": null, + "name": "www", + "version": "2017.0.1.0", + "description": "Dynamo support for interaction with the Web & REST APIs.", + "group": "radugidei", + "keywords": [ + "web", + "integration", + "rest api", + "api", + "request" + ], + "dependencies": [], + "contents": "", + "engine_version": "1.2.1.3083", + "engine": "dynamo", + "engine_metadata": "", + "site_url": "www.radugidei.com", + "repository_url": "www.github.com/radumg/DynWWW", + "contains_binaries": true, + "node_libraries": [ + "DynWWW, Version=2017.0.1.0, Culture=neutral, PublicKeyToken=null" + ] +} \ No newline at end of file diff --git a/src/DynaWeb/DynaWeb.csproj b/src/DynaWeb/DynaWeb.csproj new file mode 100644 index 0000000..7183d69 --- /dev/null +++ b/src/DynaWeb/DynaWeb.csproj @@ -0,0 +1,174 @@ + + + + + Debug + AnyCPU + {CE19C882-1AAC-434C-99AF-4A285DA053BA} + Library + Properties + DynaWeb + DynaWeb + v4.5.2 + 512 + + + true + full + false + ..\..\build\debug\ + DEBUG;TRACE + prompt + 4 + ..\..\build\debug\DynaWeb.xml + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + packages\DynamoVisualProgramming.DynamoCoreNodes.1.3.0\lib\net45\Analysis.dll + False + + + packages\DynamoVisualProgramming.DynamoCoreNodes.1.3.0\lib\net45\Display.dll + False + + + packages\DynamoVisualProgramming.DynamoCoreNodes.1.3.0\lib\net45\DSCoreNodes.dll + False + + + packages\DynamoVisualProgramming.Core.1.3.0\lib\net45\DSIronPython.dll + False + + + packages\DynamoVisualProgramming.Core.1.3.0\lib\net45\DynamoApplications.dll + False + + + packages\DynamoVisualProgramming.Core.1.3.0\lib\net45\DynamoCore.dll + False + + + packages\DynamoVisualProgramming.Core.1.3.0\lib\net45\DynamoInstallDetective.dll + False + + + packages\DynamoVisualProgramming.DynamoServices.1.3.0\lib\net45\DynamoServices.dll + False + + + packages\DynamoVisualProgramming.Core.1.3.0\lib\net45\DynamoShapeManager.dll + False + + + packages\DynamoVisualProgramming.ZeroTouchLibrary.1.3.0\lib\net45\DynamoUnits.dll + False + + + packages\DynamoVisualProgramming.Core.1.3.0\lib\net45\DynamoUtilities.dll + False + + + ..\packages\Prism.Composition.5.0.0\lib\NET45\Microsoft.Practices.Prism.Composition.dll + False + + + ..\packages\Prism.Interactivity.5.0.0\lib\NET45\Microsoft.Practices.Prism.Interactivity.dll + False + + + ..\packages\Prism.Mvvm.1.0.0\lib\net45\Microsoft.Practices.Prism.Mvvm.dll + False + + + ..\packages\Prism.Mvvm.1.0.0\lib\net45\Microsoft.Practices.Prism.Mvvm.Desktop.dll + False + + + ..\packages\Prism.PubSubEvents.1.0.0\lib\portable-sl4+wp7+windows8+net40\Microsoft.Practices.Prism.PubSubEvents.dll + False + + + ..\packages\Prism.Mvvm.1.0.0\lib\net45\Microsoft.Practices.Prism.SharedInterfaces.dll + False + + + ..\packages\CommonServiceLocator.1.3\lib\portable-net4+sl5+netcore45+wpa81+wp8\Microsoft.Practices.ServiceLocation.dll + False + + + ..\packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll + False + + + packages\DynamoVisualProgramming.DynamoServices.1.3.0\lib\net45\NodeServices2.dll + False + + + ..\packages\NUnit.3.7.1\lib\net45\nunit.framework.dll + False + + + packages\DynamoVisualProgramming.Core.1.3.0\lib\net45\ProtoCore.dll + False + + + packages\DynamoVisualProgramming.ZeroTouchLibrary.1.3.0\lib\net45\ProtoGeometry.dll + False + + + ..\packages\RestSharp.105.2.3\lib\net452\RestSharp.dll + False + + + + + + + + + + + packages\DynamoVisualProgramming.Core.1.3.0\lib\net45\VMDataBridge.dll + False + + + + + + + + + + + + True + True + Resources.resx + + + + + + + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + + (robocopy $(OutDir) C:\users\radug\desktop\testing /W:3 /R:3 /IT /IS /XF *.tmp) ^& IF %25ERRORLEVEL%25 LEQ 4 exit /B 0 + + \ No newline at end of file diff --git a/src/DynaWeb/Helpers/WebHelpers.cs b/src/DynaWeb/Helpers/WebHelpers.cs new file mode 100644 index 0000000..bff7d57 --- /dev/null +++ b/src/DynaWeb/Helpers/WebHelpers.cs @@ -0,0 +1,213 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using DynaWeb.Properties; +using Autodesk.DesignScript.Runtime; +using DynaWeb; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System.Reflection; +using Newtonsoft.Json.Converters; +using System.Dynamic; + +namespace DynaWeb +{ + public static class Helpers + { + #region URL & Uri handling + /// + /// Constructs a valid URI from a supplied string URL. Use this to both check and ensure URLs are valid + /// + /// The URL to check. + /// A valid URI if the string URL is valid, throws Exception if not. + public static Uri ParseUriFromString(string url) + { + if (string.IsNullOrEmpty(url)) + { + throw new ArgumentException(DynaWeb.Properties.Resources.WebUrlNullMessage); + } + + Uri uriResult; + var result = Uri.TryCreate(url, UriKind.Absolute, out uriResult) + && (uriResult.Scheme == Uri.UriSchemeHttp + || uriResult.Scheme == Uri.UriSchemeHttps); + + if (!result) + { + throw new UriFormatException(DynaWeb.Properties.Resources.WebUrlInvalidMessage); + } + + return uriResult; + } + + /// Check the URI is valid + /// The URI to check + /// True if is valid, False otherwise + public static Boolean CheckURI(Uri uriToCheck) + { + if (uriToCheck.IsFile || uriToCheck.IsUnc) throw new Exception("URI is file or is UNC pointing to internal network"); + + if (!Uri.CheckSchemeName(uriToCheck.Scheme)) + return false; + return true; + } + + #endregion + + #region deserialisation + + /// + /// Recursively parse a JSON token into native data types. + /// This includes all children of the JSON object, regardless of how many levels of nesting there are. + /// + /// The JSON token (object) to parse. + /// The parsed object + public static object Deserialise(string json) + { + return ParseObject(JToken.Parse(json)); + } + + /// + /// Deserialises a JSON string to a .NET object. + /// + /// The JSON string that needs to be deserialised. + /// The response deserialised as an object. + public static dynamic DeserializeAsObject(string json) + { + /// We don't want the deserialisation to break if some properties are empty. + /// So we need to specify the behaviour when such values are encountered. + JsonSerializerSettings settings = new JsonSerializerSettings(); + settings.NullValueHandling = NullValueHandling.Ignore; + settings.MissingMemberHandling = MissingMemberHandling.Ignore; + settings.CheckAdditionalContent = true; + + return JsonConvert.DeserializeObject(json, settings); + } + + /// + /// Deserialises a JSON string to the type of a supplied object. + /// + /// The object type to deserialize to. + /// The JSON string that needs to be deserialised. + /// The object that will be used to determine what type to deserialise to. + /// The response deserialised as same type as supplied object. + public static dynamic DeserializeByObjectType(string json, object obj) + { + /// We don't want the deserialisation to break if some properties are empty. + /// So we need to specify the behaviour when such values are encountered. + JsonSerializerSettings settings = new JsonSerializerSettings(); + settings.NullValueHandling = NullValueHandling.Ignore; + settings.MissingMemberHandling = MissingMemberHandling.Ignore; + settings.CheckAdditionalContent = true; + var type = obj.GetType(); + + return JsonConvert.DeserializeObject(json, type, settings); + } + /// + /// Deserialises a JSON string into a dictionary of string keys and object values. + /// Note : Does not handle deserialisation of nested objects. + /// + /// The JSON string to deserialise + /// A dictionary of the responses's JSON content. + [MultiReturn(new[] { "properties", "values" })] + public static Dictionary DeserialiseAsDictionary(string json) + { + JObject jsonObj = JObject.Parse(json); + + var props = jsonObj.Properties().Select(x => x.Name).ToList(); + var values = jsonObj.Values().Select(x => x.ToString()).ToList(); + + return new Dictionary + { + { "properties", props }, + { "values", values } + }; + } + + /// + /// Serialises an object string to a JSON string. + /// + /// The object that will be serialised. + /// Object serialised as JSON string. + public static string SerializeToJSON(object obj) + { + /// We don't want the serialisation to break if some properties are empty. + /// So we need to specify the behaviour when such values are encountered. + JsonSerializerSettings settings = new JsonSerializerSettings(); + settings.NullValueHandling = NullValueHandling.Ignore; + settings.MissingMemberHandling = MissingMemberHandling.Ignore; + settings.CheckAdditionalContent = true; + settings.Formatting = Formatting.Indented; + + return JsonConvert.SerializeObject(obj, settings); + } + + /// + /// Builds a new JSON string from the given root of an existing JSON object. + /// + /// The existing JSON + /// The name of the root object to return as JSON. + /// The new JSON string + public static string SelectJsonRoot(string json, string root) + { + if (string.IsNullOrEmpty(json) || string.IsNullOrEmpty(root)) throw new ArgumentNullException(); + return JObject.Parse(json).SelectToken(root).ToString(); + } + + /// + /// This method will recursively parse a JSON token into native .NET types. + /// + /// The JSON token (object) to parse. + /// The parsed object. + private static object ParseObject(JToken token) + { + switch (token.Type) + { + case JTokenType.Object: + return token.Children() + .ToDictionary(prop => prop.Name, + prop => ParseObject(prop.Value)); + + case JTokenType.Array: + return token.Select(ParseObject).ToList(); + + default: + return ((JValue)token).Value; + } + } + + #endregion + + #region Type support + + /// + /// Gets only non-null properties and their values from a Type using Reflection. + /// + /// The object to extract type properties from. + /// A dictionary of properties and their values. + internal static Dictionary GetValidProperties(object obj) + { + var parameters = new Dictionary(); + Type type = obj.GetType(); + foreach (PropertyInfo prop in type.GetProperties()) + { + var value = prop.GetValue(obj).ToString(); + if (!string.IsNullOrEmpty(value)) parameters.Add(prop.Name, value); + } + return parameters; + } + + internal static dynamic WrapObject(object obj) + { + var wrapper = new + { + data = obj + }; + var json = JsonConvert.SerializeObject(wrapper); + return ParseObject(JToken.Parse(json)); + } + #endregion + } +} diff --git a/src/DynaWeb/NodeModels/WebRequestModel.cs b/src/DynaWeb/NodeModels/WebRequestModel.cs new file mode 100644 index 0000000..3463638 --- /dev/null +++ b/src/DynaWeb/NodeModels/WebRequestModel.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Dynamo.Graph.Nodes; +using DynaWeb.Properties; +using ProtoCore.AST.AssociativeAST; +using DynaWeb; + +namespace CoreNodeModels.Web +{ + /* + [NodeName("Web Request")] + [NodeDescription("WebRequestDescription", typeof(Resources))] + [NodeCategory(BuiltinNodeCategories.CORE_WEB)] + [IsDesignScriptCompatible] + [AlsoKnownAs("DynaWebNodesUI.WebRequest")] + public class SimpleWebRequest : NodeModel + { + public SimpleWebRequest() + { + InPortData.Add(new PortData("url", Resources.WebRequestPortDataUrlToolTip)); + OutPortData.Add(new PortData("result", Resources.WebRequestPortDataResultToolTip)); + RegisterAllPorts(); + + CanUpdatePeriodically = true; + } + + public override IEnumerable BuildOutputAst(List inputAstNodes) + { + var functionCall = AstFactory.BuildFunctionCall( + new Func(WebRequest.Execute), + inputAstNodes + ); + + return new[] { AstFactory.BuildAssignment(GetAstIdentifierForOutputIndex(0), functionCall) }; + } + } + */ +} diff --git a/src/DynaWeb/Properties/AssemblyInfo.cs b/src/DynaWeb/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..a9dada4 --- /dev/null +++ b/src/DynaWeb/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +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("DynaWeb")] +[assembly: AssemblyDescription("DynaWeb is a Dynamo package providing support for interaction with the interwebz in general and with REST APIs in particular.")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Radu Gidei")] +[assembly: AssemblyProduct("DynaWeb")] +[assembly: AssemblyCopyright("Copyright Radu Gidei © 2017")] +[assembly: AssemblyTrademark("Radu Gidei © 2017")] +[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("ce19c882-1aac-434c-99af-4a285da053ba")] + +// 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.*")] +[assembly: AssemblyVersion("1.0.3.0")] +[assembly: AssemblyFileVersion("1.0.3.0")] diff --git a/src/DynaWeb/Properties/Resources.Designer.cs b/src/DynaWeb/Properties/Resources.Designer.cs new file mode 100644 index 0000000..a81aed2 --- /dev/null +++ b/src/DynaWeb/Properties/Resources.Designer.cs @@ -0,0 +1,252 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace DynaWeb.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("DynaWeb.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Could not build a valid Uri for the request. Please double-check the WebClient baseUrl and the WebRequest resource.. + /// + internal static string WebClientBuildUrlFailed { + get { + return ResourceManager.GetString("WebClientBuildUrlFailed", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The value for MaxRedirects cannot be negative or equal to zero.. + /// + internal static string WebClientMaxRedirectsInvalidMessage { + get { + return ResourceManager.GetString("WebClientMaxRedirectsInvalidMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The provided WebClient cannot be null.. + /// + internal static string WebClientNullMessage { + get { + return ResourceManager.GetString("WebClientNullMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The provided request cannot be null.. + /// + internal static string WebClientRequestNullMessage { + get { + return ResourceManager.GetString("WebClientRequestNullMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The value for Timeout cannot be negative or equal to zero.. + /// + internal static string WebClientTimeoutInvalidMessage { + get { + return ResourceManager.GetString("WebClientTimeoutInvalidMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The token cannot be null.. + /// + internal static string WebClientTokenNullMessage { + get { + return ResourceManager.GetString("WebClientTokenNullMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The base URL for the client cannot be null.. + /// + internal static string WebClientUrlNullMessage { + get { + return ResourceManager.GetString("WebClientUrlNullMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The UserAgent provided cannot be null.. + /// + internal static string WebClientUserAgentNullMessage { + get { + return ResourceManager.GetString("WebClientUserAgentNullMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The file provided could not be read.. + /// + internal static string WebIOFileNotRead { + get { + return ResourceManager.GetString("WebIOFileNotRead", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The supplied JSON token is not valid.. + /// + internal static string WebJsonTokenInvalidMessage { + get { + return ResourceManager.GetString("WebJsonTokenInvalidMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The resource/endpoint provided cannot be null.. + /// + internal static string WebRequestEndpointNullMessage { + get { + return ResourceManager.GetString("WebRequestEndpointNullMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The provided WebRequest cannot be null.. + /// + internal static string WebRequestNullMessage { + get { + return ResourceManager.GetString("WebRequestNullMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The parameter provided cannot be null.. + /// + internal static string WebRequestParameterNullMessage { + get { + return ResourceManager.GetString("WebRequestParameterNullMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to . + /// + internal static string WebRequestPortDataResultToolTip { + get { + return ResourceManager.GetString("WebRequestPortDataResultToolTip", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to . + /// + internal static string WebRequestPortDataUrlToolTip { + get { + return ResourceManager.GetString("WebRequestPortDataUrlToolTip", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The web request timed out and did not complete. Data may or may not have reached the remote server.. + /// + internal static string WebRequestTimedOutMessage { + get { + return ResourceManager.GetString("WebRequestTimedOutMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The web request was aborted before it could be completed. Data may or may not have reached the remote server.. + /// + internal static string WebResponseAbortedMessage { + get { + return ResourceManager.GetString("WebResponseAbortedMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to A network transport error occured. Possible causes include network is down, failed DNS lookup, etc. This also means the request did not reach the intended recipient.. + /// + internal static string WebResponseNetworkErrorMessage { + get { + return ResourceManager.GetString("WebResponseNetworkErrorMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The web request did not return a valid response status.. + /// + internal static string WebResponseNoneMessage { + get { + return ResourceManager.GetString("WebResponseNoneMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The URL provided is not a valid URL.. + /// + internal static string WebUrlInvalidMessage { + get { + return ResourceManager.GetString("WebUrlInvalidMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The URL cannot be null.. + /// + internal static string WebUrlNullMessage { + get { + return ResourceManager.GetString("WebUrlNullMessage", resourceCulture); + } + } + } +} diff --git a/src/DynaWeb/Properties/Resources.resx b/src/DynaWeb/Properties/Resources.resx new file mode 100644 index 0000000..ebb5d76 --- /dev/null +++ b/src/DynaWeb/Properties/Resources.resx @@ -0,0 +1,183 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Could not build a valid Uri for the request. Please double-check the WebClient baseUrl and the WebRequest resource. + + + The value for MaxRedirects cannot be negative or equal to zero. + + + The provided WebClient cannot be null. + + + The provided request cannot be null. + + + The value for Timeout cannot be negative or equal to zero. + + + The token cannot be null. + + + The base URL for the client cannot be null. + + + The UserAgent provided cannot be null. + + + The file provided could not be read. + + + The supplied JSON token is not valid. + + + The resource/endpoint provided cannot be null. + + + The provided WebRequest cannot be null. + + + The parameter provided cannot be null. + + + + + + + + + The web request timed out and did not complete. Data may or may not have reached the remote server. + + + The web request was aborted before it could be completed. Data may or may not have reached the remote server. + + + A network transport error occured. Possible causes include network is down, failed DNS lookup, etc. This also means the request did not reach the intended recipient. + + + The web request did not return a valid response status. + + + The URL provided is not a valid URL. + + + The URL cannot be null. + + \ No newline at end of file diff --git a/src/DynaWeb/app.config b/src/DynaWeb/app.config new file mode 100644 index 0000000..4fc378d --- /dev/null +++ b/src/DynaWeb/app.config @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/src/DynaWeb/packages.config b/src/DynaWeb/packages.config new file mode 100644 index 0000000..eaebdf9 --- /dev/null +++ b/src/DynaWeb/packages.config @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/DynaWeb.Core.Tests/Class1.cs b/test/DynaWeb.Core.Tests/Class1.cs new file mode 100644 index 0000000..4dd88a6 --- /dev/null +++ b/test/DynaWeb.Core.Tests/Class1.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace DynaWeb.Core.Tests +{ + public class Class1 + { + } +} diff --git a/test/DynaWeb.Core.Tests/DynaWeb.Core.Tests.csproj b/test/DynaWeb.Core.Tests/DynaWeb.Core.Tests.csproj new file mode 100644 index 0000000..0bf7a22 --- /dev/null +++ b/test/DynaWeb.Core.Tests/DynaWeb.Core.Tests.csproj @@ -0,0 +1,56 @@ + + + + + Debug + AnyCPU + {50A649C6-FB32-4C6D-A7B6-6EB99DE8547D} + Library + Properties + DynWWW.Core.Tests + DynWWW.Core.Tests + v4.5.2 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\..\src\packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll + + + ..\..\src\packages\RestSharp.105.2.3\lib\net452\RestSharp.dll + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/DynaWeb.Core.Tests/Properties/AssemblyInfo.cs b/test/DynaWeb.Core.Tests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..1591b51 --- /dev/null +++ b/test/DynaWeb.Core.Tests/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +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("DynaWeb.Core.Tests")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("DynaWeb.Core.Tests")] +[assembly: AssemblyCopyright("Copyright © 2017")] +[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("50a649c6-fb32-4c6d-a7b6-6eb99de8547d")] + +// 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.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/test/DynaWeb.Core.Tests/packages.config b/test/DynaWeb.Core.Tests/packages.config new file mode 100644 index 0000000..d186dc1 --- /dev/null +++ b/test/DynaWeb.Core.Tests/packages.config @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/test/README.md b/test/README.md new file mode 100644 index 0000000..3a1303d --- /dev/null +++ b/test/README.md @@ -0,0 +1 @@ +This folder houses all the testing (unit, integration, etc) code for the DynWWW package.