/// <summary> /// Checks if the provided repository path exists and creates the missing containers all the way up to the root. /// </summary> /// <param name="path">The path to create containers for.</param> /// <param name="containerTypeName">Optional: content type name of created containers. Default is Folder.</param> /// <param name="server">Target server.</param> /// <returns>The newly created container. If it already exists, this method returns null.</returns> public static async Task <Content> EnsurePathAsync(string path, string containerTypeName = null, ServerContext server = null) { if (string.IsNullOrEmpty(path) || string.CompareOrdinal(path, "/Root") == 0) { return(null); } if (await Content.ExistsAsync(path, server).ConfigureAwait(false)) { return(null); } var parentPath = RepositoryPath.GetParentPath(path); // ensure parent await EnsurePathAsync(parentPath, containerTypeName, server).ConfigureAwait(false); var name = RepositoryPath.GetFileName(path); var folder = Content.CreateNew(parentPath, containerTypeName ?? "Folder", name, null, server); try { await folder.SaveAsync().ConfigureAwait(false); return(folder); } catch (ClientException ex) { // this particular exception is not an error, so move on if (ex.ErrorData.ExceptionType == "NodeAlreadyExistsException") { return(null); } throw; } }
private static HttpWebRequest CreateChunkUploadWebRequest(string url, ServerContext server, UploadData uploadData, string boundary, out Stream requestStream) { var myRequest = GetRequest(url, server); myRequest.Method = "POST"; myRequest.ContentType = "multipart/form-data; boundary=" + boundary; myRequest.Headers.Add("Content-Disposition", "attachment; filename=\"" + Uri.EscapeUriString(uploadData.FileName) + "\""); var boundarybytes = Encoding.ASCII.GetBytes("\r\n--" + boundary + "\r\n"); // we must not close the stream after this as we need to write the chunk into it in the caller method requestStream = myRequest.GetRequestStream(); //write form data values foreach (var kvp in uploadData.ToDictionary()) { requestStream.Write(boundarybytes, 0, boundarybytes.Length); var formitem = string.Format(UPLOAD_FORMDATA_TEMPLATE, kvp.Key, kvp.Value); var formitembytes = Encoding.UTF8.GetBytes(formitem); requestStream.Write(formitembytes, 0, formitembytes.Length); } //write a boundary requestStream.Write(boundarybytes, 0, boundarybytes.Length); //write file name and content type var header = string.Format(UPLOAD_HEADER_TEMPLATE, "files[]", Uri.EscapeUriString(uploadData.FileName)); var headerbytes = Encoding.UTF8.GetBytes(header); requestStream.Write(headerbytes, 0, headerbytes.Length); return(myRequest); }
/// <summary> /// Loads children of a container. /// </summary> /// <param name="path">Path of the container.</param> /// <param name="server">Target server.</param> /// <returns></returns> public static async Task <IEnumerable <Content> > LoadCollectionAsync(string path, ServerContext server = null) { return(await RESTCaller.GetCollectionAsync(path, server)); }
/// <summary> /// Gets the raw response of a general HTTP request from the server. /// </summary> /// <param name="uri">Request URI.</param> /// <param name="method">HTTP method (SenseNet.Client.HttpMethods class has a few predefined methods).</param> /// <param name="body">Request body.</param> /// <param name="server">Target server.</param> /// <returns>Raw HTTP response.</returns> public static async Task <string> GetResponseStringAsync(Uri uri, ServerContext server = null, HttpMethod method = null, string body = null) { var retryCount = 0; while (retryCount < REQUEST_RETRY_COUNT) { SnTrace.Category(ClientContext.TraceCategory).Write("###>REQ: {0}", uri); var myRequest = GetRequest(uri, server); if (method != null) { myRequest.Method = method.Method; } if (!string.IsNullOrEmpty(body)) { try { using (var requestWriter = new StreamWriter(myRequest.GetRequestStream())) { requestWriter.Write(body); } } catch (Exception ex) { throw new ClientException("Error during writing to request stream: " + ex.Message, ex); } } else { myRequest.ContentLength = 0; } try { using (var wr = await myRequest.GetResponseAsync()) { return(await ReadResponseStringAsync(wr)); } } catch (WebException ex) { var webResponse = ex.Response as HttpWebResponse; // a 404 result is not an error in case of simple get requests, so return silently if (webResponse != null && webResponse.StatusCode == HttpStatusCode.NotFound && (method == null || method != HttpMethod.Post)) { return(null); } if (retryCount >= REQUEST_RETRY_COUNT - 1) { throw await GetClientExceptionAsync(ex, uri.ToString(), method, body); } else { var responseString = await ReadResponseStringAsync(ex.Response); Thread.Sleep(50); SnTrace.Category(ClientContext.TraceCategory).Write("###>REQ: {0} ERROR:{1}", uri, responseString.Replace(Environment.NewLine, " ").Replace("\r\n", " ") + " " + ex); } } retryCount++; } return(string.Empty); }
/// <summary> /// Loads a content from the server. Use this method to specify a detailed /// content request, for example wich fields you want to expand or select. /// </summary> /// <param name="requestData">Detailed information that will be sent as part of the request.</param> /// <param name="server">Target server.</param> public static async Task <Content> LoadAsync(ODataRequest requestData, ServerContext server = null) { return(await RESTCaller.GetContentAsync(requestData, server)); }
//----------------------------------------------------------------------------- Security /// <summary> /// Checks whether a user has the provided permissions on the content. /// </summary> /// <param name="permissions">Permission names to check.</param> /// <param name="user">The user who's permissions need to be checked. If it is not provided, the server checks the current user.</param> /// <param name="server">Target server.</param> public async Task <bool> HasPermissionAsync(string[] permissions, string user = null, ServerContext server = null) { return(await SecurityManager.HasPermissionAsync(this.Id, permissions, user, server)); }
//============================================================================= Constructors /// <summary> /// Internal constructor for client content. /// </summary> /// <param name="server">Target server. If null, the first one will be used from the configuration.</param> protected Content(ServerContext server) { Server = server ?? ClientContext.Current.Servers[0]; _fields = new Dictionary <string, object>(); }
/// <summary> /// Executes a query on the server and returns results filtered and expanded /// based on the provided parameters. /// </summary> /// <param name="queryText">Content query text.</param> /// <param name="select">Fields to select.</param> /// <param name="expand">Fields to expand.</param> /// <param name="settings">Query settings.</param> /// <param name="server">Target server.</param> public static async Task <IEnumerable <Content> > QueryAsync(string queryText, string[] select = null, string[] expand = null, QuerySettings settings = null, ServerContext server = null) { if (settings == null) { settings = QuerySettings.Default; } var oreq = new ODataRequest { Path = "/Root", Select = select, Expand = expand, Top = settings.Top, Skip = settings.Skip, SiteUrl = ServerContext.GetUrl(server) }; oreq.Parameters.Add("query", Uri.EscapeDataString(queryText)); if (settings.EnableAutofilters != FilterStatus.Default) { oreq.Parameters.Add("enableautofilters", settings.EnableAutofilters.ToString().ToLower()); } if (settings.EnableLifespanFilter != FilterStatus.Default) { oreq.Parameters.Add("enablelifespanfilter", settings.EnableLifespanFilter.ToString().ToLower()); } return(await Content.LoadCollectionAsync(oreq, server)); }
private static async Task SaveAndFinalizeBlobInternalAsync(string initResponse, long fileSize, Func <int, int, string, Task> blobCallback, string fileName = null, string propertyName = null, ServerContext server = null) { // parse the response of the initial request var response = JsonHelper.Deserialize(initResponse); int contentId = response.id; string token = response.token; int versionId = response.versionId; // save binary through the blob storage await blobCallback(contentId, versionId, token); // send final request await RESTCaller.GetResponseStringAsync(contentId, "FinalizeBlobUpload", HttpMethod.Post, JsonHelper.Serialize(new { token, fullSize = fileSize, fieldName = propertyName, fileName }), server); }
/// <summary> /// Queries the server for content items using the provided request data. /// </summary> /// <param name="requestData">Detailed information that will be sent as part of the request. /// For example Top, Skip, Select, etc.</param> /// <param name="server">Target server.</param> public static async Task <IEnumerable <Content> > LoadCollectionAsync(ODataRequest requestData, ServerContext server = null) { return(await RESTCaller.GetCollectionAsync(requestData, server)); }
private static async Task <IEnumerable <Content> > LoadReferencesAsync(string path, int id, string fieldName, string[] select = null, ServerContext server = null) { if (select == null || select.Length == 0) { select = new[] { "*" } } ; var projection = new[] { "Id", "Path", "Type" }; projection = projection.Union(select.Select(p => fieldName + "/" + p)).ToArray(); var oreq = new ODataRequest { SiteUrl = ServerContext.GetUrl(server), Expand = new[] { fieldName }, Select = projection, ContentId = id, Path = path }; dynamic content = await Content.LoadAsync(oreq, server); // we assume that this is an array of content json objects var items = (JArray)content[fieldName]; return(items.Select(c => CreateFromResponse(c, server))); }
/// <summary> /// Uploads a file to the server into the provided container. /// </summary> /// <param name="binaryStream">File contents.</param> /// <param name="uploadData">Upload parameters.</param> /// <param name="parentPath">Parent path.</param> /// <param name="server">Target server.</param> /// <param name="progressCallback">An optional callback method that is called after each chunk is uploaded to the server.</param> /// <returns>The uploaded file content returned at the end of the upload request.</returns> public static async Task <Content> UploadAsync(Stream binaryStream, UploadData uploadData, string parentPath, ServerContext server = null, Action <int> progressCallback = null) { var requestData = new ODataRequest() { SiteUrl = ServerContext.GetUrl(server), ActionName = "Upload", Path = parentPath }; return(await UploadInternalAsync(binaryStream, uploadData, requestData, server, progressCallback)); }
private static async Task <Content> UploadInternalAsync(Stream binaryStream, UploadData uploadData, ODataRequest requestData, ServerContext server = null, Action <int> progressCallback = null) { // force set values uploadData.UseChunk = binaryStream.Length > ClientContext.Current.ChunkSizeInBytes; if (uploadData.FileLength == 0) { uploadData.FileLength = binaryStream.Length; } requestData.Parameters["create"] = "1"; dynamic uploadedContent = null; var retryCount = 0; // send initial request while (retryCount < REQUEST_RETRY_COUNT) { try { var myReq = CreateInitUploadWebRequest(requestData.ToString(), server, uploadData); SnTrace.Category(ClientContext.TraceCategory).Write("###>REQ: {0}", myReq.RequestUri); using (var wr = await myReq.GetResponseAsync()) { uploadData.ChunkToken = await ReadResponseStringAsync(wr); } // succesful request: skip out from retry loop break; } catch (WebException ex) { if (retryCount >= REQUEST_RETRY_COUNT - 1) { var ce = new ClientException("Error during binary upload.", ex); ce.Data["SiteUrl"] = requestData.SiteUrl; ce.Data["Parent"] = requestData.ContentId != 0 ? requestData.ContentId.ToString() : requestData.Path; ce.Data["FileName"] = uploadData.FileName; ce.Data["ContentType"] = uploadData.ContentType; throw ce; } else { Thread.Sleep(50); } } retryCount++; } var boundary = "---------------------------" + DateTime.UtcNow.Ticks.ToString("x"); var trailer = Encoding.ASCII.GetBytes("\r\n--" + boundary + "--\r\n"); // send subsequent requests var buffer = new byte[ClientContext.Current.ChunkSizeInBytes]; int bytesRead; var start = 0; // reuse previous request data, but remove unnecessary parameters requestData.Parameters.Remove("create"); while ((bytesRead = binaryStream.Read(buffer, 0, buffer.Length)) != 0) { retryCount = 0; //get the request object for the actual chunk while (retryCount < REQUEST_RETRY_COUNT) { Stream requestStream = null; HttpWebRequest chunkRequest; try { chunkRequest = CreateChunkUploadWebRequest(requestData.ToString(), server, uploadData, boundary, out requestStream); SnTrace.Category(ClientContext.TraceCategory).Write("###>REQ: {0}", chunkRequest.RequestUri); if (uploadData.UseChunk) { chunkRequest.Headers.Set("Content-Range", string.Format("bytes {0}-{1}/{2}", start, start + bytesRead - 1, binaryStream.Length)); } //write the chunk into the request stream requestStream.Write(buffer, 0, bytesRead); requestStream.Write(trailer, 0, trailer.Length); await requestStream.FlushAsync(); } finally { if (requestStream != null) { requestStream.Close(); } } //send the request try { using (var wr = await chunkRequest.GetResponseAsync()) { var rs = await ReadResponseStringAsync(wr); uploadedContent = JsonHelper.Deserialize(rs); } // successful request: skip out from the retry loop break; } catch (WebException ex) { if (retryCount >= REQUEST_RETRY_COUNT - 1) { var ce = new ClientException("Error during binary upload.", ex); ce.Data["SiteUrl"] = requestData.SiteUrl; ce.Data["Parent"] = requestData.ContentId != 0 ? requestData.ContentId.ToString() : requestData.Path; ce.Data["FileName"] = uploadData.FileName; ce.Data["ContentType"] = uploadData.ContentType; throw ce; } else { Thread.Sleep(50); } } retryCount++; } start += bytesRead; // notify the caller about every chunk that was uploaded successfully if (progressCallback != null) { progressCallback(start); } } if (uploadedContent == null) { return(null); } int contentId = uploadedContent.Id; var content = Content.Create(contentId); content.Name = uploadedContent.Name; content.Path = uploadedContent.Url; return(content); }
private static async Task <dynamic> PostContentInternalAsync(int contentId, object postData, HttpMethod method, ServerContext server = null) { var reqData = new ODataRequest() { SiteUrl = ServerContext.GetUrl(server), ContentId = contentId }; var rs = await GetResponseStringAsync(reqData.GetUri(), server, method, JsonHelper.GetJsonPostModel(postData)); return(JsonHelper.Deserialize(rs).d); }
/// <summary> /// Sends a PUT OData request to the server containing the specified data. /// </summary> /// <param name="path">Content path</param> /// <param name="postData">A .NET object to serialize as post data.</param> /// <param name="server">Target server.</param> /// <returns>A deserialized dynamic JSON object parsed from the response.</returns> public static async Task <dynamic> PutContentAsync(string path, object postData, ServerContext server = null) { return(await PostContentInternalAsync(path, postData, HttpMethod.Put, server)); }
/// <summary> /// Sends a PATCH OData request to the server containing the specified data. /// </summary> /// <param name="contentId">Content id</param> /// <param name="postData">A .NET object to serialize as post data.</param> /// <param name="server">Target server.</param> /// <returns>A deserialized dynamic JSON object parsed from the response.</returns> public static async Task <dynamic> PatchContentAsync(int contentId, object postData, ServerContext server = null) { return(await PostContentInternalAsync(contentId, postData, HttpMethods.PATCH, server)); }
/// <summary> /// Assembles an http request that gets a stream from the portal containing binary data. /// Use this inside a using block to asynchronously get the response stream. /// Please catch WebExceptions and parse them using the GetClientExceptionAsync method. /// </summary> /// <param name="id">Content id.</param> /// <param name="version">Content version (e.g. V2.3D). If not provided, the highest version /// accessible to the current user will be served.</param> /// <param name="propertyName">Binary field name. Default is Binary.</param> /// <param name="server">Target server.</param> public static HttpWebRequest GetStreamRequest(int id, string version = null, string propertyName = null, ServerContext server = null) { var url = $"{ServerContext.GetUrl(server)}/binaryhandler.ashx?nodeid={id}&propertyname={propertyName ?? "Binary"}"; if (!string.IsNullOrEmpty(version)) { url += "&version=" + version; } return(GetRequest(url, server)); }
/// <summary> /// Gets the response of an OData request as a dynamic JSON object. /// </summary> /// <param name="requestData">OData request parameters, for example select or expand.</param> /// <param name="server">Target server.</param> /// <param name="method">Http method (e.g. Post). Default is Get.</param> /// <param name="postData">An object containing properties to be sent in the body. It will be serialized to JSON.</param> /// <returns>A dynamic JSON object deserialized from the response.</returns> public static async Task <dynamic> GetResponseJsonAsync(ODataRequest requestData, ServerContext server = null, HttpMethod method = null, object postData = null) { // it wouldn't work if we tried to post some data with the default GET verb if (postData != null && method == null) { method = HttpMethod.Post; } var rs = await GetResponseStringAsync(requestData.GetUri(), server, method, postData == null?null : JsonHelper.Serialize(postData)); try { return(JsonHelper.Deserialize(rs)); } catch (Exception) { throw new ClientException(string.Format("Invalid response. Request: {0}. Response: {1}", requestData.GetUri(), rs)); } }
/// <summary> /// Loads referenced content from a reference field. /// </summary> /// <param name="path">Content path.</param> /// <param name="fieldName">Reference field name.</param> /// <param name="select">Field names of the referenced content items to select.</param> /// <param name="server">Target server.</param> public static async Task <IEnumerable <Content> > LoadReferencesAsync(string path, string fieldName, string[] select = null, ServerContext server = null) { return(await LoadReferencesAsync(path, 0, fieldName, select, server)); }
private static HttpWebRequest GetRequest(string url, ServerContext server) { return(GetRequest(new Uri(url), server)); }
/// <summary> /// Executes a query on the server and returns results filtered and expanded /// based on the provided parameters. Both lifespan and system content filters /// are disabled. /// </summary> /// <param name="queryText">Content query text.</param> /// <param name="select">Fields to select.</param> /// <param name="expand">Fields to expand.</param> /// <param name="settings">Query settings.</param> /// <param name="server">Target server.</param> public static async Task <IEnumerable <Content> > QueryForAdminAsync(string queryText, string[] select = null, string[] expand = null, QuerySettings settings = null, ServerContext server = null) { if (settings == null) { settings = new QuerySettings(); } settings.EnableAutofilters = FilterStatus.Disabled; settings.EnableLifespanFilter = FilterStatus.Disabled; return(await QueryAsync(queryText, select, expand, settings, server)); }
/// <summary> /// Loads children of a container. /// </summary> /// <param name="path">Content path.</param> /// <param name="server">Target server.</param> public static async Task <IEnumerable <Content> > GetCollectionAsync(string path, ServerContext server = null) { return(await GetCollectionAsync(new ODataRequest() { SiteUrl = ServerContext.GetUrl(server), Path = path, IsCollectionRequest = true })); }
/// <summary> /// Uploads a file to the server into the provided container. /// </summary> /// <param name="parentId">Parent id.</param> /// <param name="fileName">Name of the file to upload.</param> /// <param name="stream">File contents.</param> /// <param name="contentType">Content type of the file. Default is determined by the container.</param> /// <param name="propertyName">Name of the field to upload to. Default is Binary.</param> /// <param name="server">Target server.</param> /// <param name="progressCallback">An optional callback method that is called after each chunk is uploaded to the server.</param> /// <returns>The uploaded file content returned at the end of the upload request.</returns> public static async Task <Content> UploadAsync(int parentId, string fileName, Stream stream, string contentType = null, string propertyName = null, ServerContext server = null, Action <int> progressCallback = null) { var uploadData = new UploadData() { FileName = fileName, FileLength = stream.Length }; if (!string.IsNullOrEmpty(contentType)) { uploadData.ContentType = contentType; } if (!string.IsNullOrEmpty(propertyName)) { uploadData.PropertyName = propertyName; } return(await RESTCaller.UploadAsync(stream, uploadData, parentId, server, progressCallback)); }
/// <summary> /// Creates a new content in memory without saving it. /// </summary> /// <param name="parentPath">Parent content path in the Content Repository.</param> /// <param name="contentType">Content type name.</param> /// <param name="name">Name of the new content.</param> /// <param name="contentTemplate">Content template path.</param> /// <param name="server">Target server.</param> public static Content CreateNew(string parentPath, string contentType, string name, string contentTemplate = null, ServerContext server = null) { return(CreateNew <Content>(parentPath, contentType, name, contentTemplate, server)); }
/// <summary> /// Gets a blob storage token that identifies a binary in the storage. /// </summary> /// <param name="path">Content path.</param> /// <param name="version">Content version (e.g. V2.3D). If not provided, the highest version /// accessible to the current user will be served.</param> /// <param name="propertyName">Binary field name. Default is Binary.</param> /// <param name="server">Target server.</param> /// <returns>A token that can be used with the Blob storage API.</returns> public static async Task <string> GetBlobToken(string path, string version = null, string propertyName = null, ServerContext server = null) { var responseText = await RESTCaller.GetResponseStringAsync(path, "GetBinaryToken", HttpMethod.Post, JsonHelper.Serialize(new { version, fieldName = propertyName }), server); var response = JsonHelper.Deserialize(responseText); return(response.token); }
/// <summary> /// Creates a new specialized content in memory without saving it. /// </summary> /// <typeparam name="T">One of the specialized client content types inheriting from Content (e.g. Group).</typeparam> /// <param name="parentPath">Parent content path in the Content Repository.</param> /// <param name="contentType">Content type name.</param> /// <param name="name">Name of the new content.</param> /// <param name="contentTemplate">Content template path.</param> /// <param name="server">Target server.</param> public static T CreateNew <T>(string parentPath, string contentType, string name, string contentTemplate = null, ServerContext server = null) where T : Content { if (string.IsNullOrEmpty(parentPath)) { throw new ArgumentNullException(nameof(parentPath)); } if (string.IsNullOrEmpty(contentType)) { throw new ArgumentNullException(nameof(contentType)); } var ctor = typeof(T).GetConstructor( BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { typeof(ServerContext) }, null); dynamic dc = ctor.Invoke(new object[] { server }) as T; if (dc == null) { throw new ClientException("Constructor not found or type could not be initialized. " + typeof(T).FullName); } dc.ParentPath = parentPath; dc.Name = name; dc.Existing = false; // set dynamic properties dc.__ContentType = contentType; if (!string.IsNullOrEmpty(contentTemplate)) { dc.__ContentTemplate = contentTemplate; } return(dc); }
/// <summary> /// Removes permission break on the content. /// </summary> /// <param name="server">Target server.</param> public async Task UnbreakInheritanceAsync(ServerContext server = null) { await SecurityManager.UnbreakInheritanceAsync(this.Id, server); }
//============================================================================= Static API /// <summary> /// Loads a content from the server. /// </summary> /// <param name="id">Content id.</param> /// <param name="server">Target server.</param> public static async Task <Content> LoadAsync(int id, ServerContext server = null) { return(await RESTCaller.GetContentAsync(id, server)); }
/// <summary> /// Internal constructor for client content. /// </summary> /// <param name="server">Target server. If null, the first one will be used from the configuration.</param> /// <param name="responseContent">A JSON response that contains content fields.</param> protected Content(ServerContext server, dynamic responseContent) : this(server) { InitializeFromResponse(responseContent); }
/// <summary> /// Loads a content from the server. /// </summary> /// <param name="path">Content path.</param> /// <param name="server">Target server.</param> public static async Task <Content> LoadAsync(string path, ServerContext server = null) { return(await RESTCaller.GetContentAsync(path, server)); }