Skip to content
This repository has been archived by the owner on Nov 9, 2017. It is now read-only.

PostAttachmentsOperation

davetimmins edited this page Nov 20, 2014 · 2 revisions

If you want to post attachments then the in built gateway types won't work since they don't set the request body to the appropriate content.

Posting attachments requires a multipart form content body with some custom headers so you can use the code below.

Define the request and response types

public interface IAttachment
{
    byte[] Attachment { get; }

    string FileName { get; }

    string ContentType { get; }
}

public class AttachmentToPost : ArcGISServerOperation, IAttachment
{
    public AttachmentToPost(ArcGISServerEndpoint endpoint, long objectID, string attachmentBase64Encoded, string fileName, string contentType, bool isUpdate = false)
        : this(endpoint, objectID, fileName, contentType, isUpdate)
    {
        Guard.AgainstNullArgument("attachmentBase64Encoded", attachmentBase64Encoded);

        AttachmentBase64Encoded = attachmentBase64Encoded;
        Attachment = Convert.FromBase64String(AttachmentBase64Encoded);
    }

    public AttachmentToPost(ArcGISServerEndpoint endpoint, long objectID, byte[] attachment, string fileName, string contentType, bool isUpdate = false)
        : this(endpoint, objectID, fileName, contentType, isUpdate)
    {
        Guard.AgainstNullArgument("attachment", attachment);

        Attachment = attachment;
        AttachmentBase64Encoded = Convert.ToBase64String(Attachment);
    }

    AttachmentToPost(ArcGISServerEndpoint endpoint, long objectID, string fileName, string contentType, bool isUpdate = false)
    {
        Guard.AgainstNullArgument("endpoint", endpoint);

        Endpoint = new ArcGISServerEndpoint(endpoint.RelativeUrl.Trim('/')
            + string.Format("/{0}/{1}", objectID, isUpdate ? "updateAttachment" : "addAttachment"));

        ObjectID = objectID;
        FileName = fileName;
        ContentType = contentType;
    }

    public long ObjectID { get; private set; }

    public string AttachmentBase64Encoded { get; private set; }

    public byte[] Attachment { get; private set; }

    public string FileName { get; private set; }

    public string ContentType { get; private set; }
}

[DataContract]
public class AddAttachmentResponse : PortalResponse
{
    [DataMember(Name = "addAttachmentResult")]
    public PostAttachmentResult AddAttachmentResult { get; set; }
}

[DataContract]
public class PostAttachmentResult : PortalResponse
{
    [DataMember(Name = "objectId")]
    public long ObjectID { get; set; }

    [DataMember(Name = "globalId")]
    public string GlobalID { get; set; }

    [DataMember(Name = "success")]
    public bool Success { get; set; }
}

and then the gateway

public class AttachmentGateway : IPortalGateway, IDisposable
{
    HttpClient _httpClient;

    /// <summary>
    /// Create an ArcGIS Server gateway to access secure resources
    /// </summary>
    /// <param name="rootUrl">Made up of scheme://host:port/site</param>
    /// <param name="serializer">Used to (de)serialize requests and responses</param>
    /// <param name="tokenProvider">Provide access to a token for secure resources</param>
    public AttachmentGateway(string rootUrl, ISerializer serializer = null, ITokenProvider tokenProvider = null)
    {
        RootUrl = rootUrl.AsRootUrl();
        TokenProvider = tokenProvider;
        Serializer = serializer ?? SerializerFactory.Get();
        if (Serializer == null) throw new ArgumentNullException("serializer", "Serializer has not been set.");

        _httpClient = HttpClientFactory.Get();
    }

    ~AttachmentGateway()
    {
        Dispose(false);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (disposing)
        {
            if (_httpClient != null)
            {
                _httpClient.Dispose();
                _httpClient = null;
            }
        }
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    public string RootUrl { get; private set; }

    public ITokenProvider TokenProvider { get; private set; }

    public ISerializer Serializer { get; private set; }

    async Task<Token> CheckGenerateToken(CancellationToken ct)
    {
        if (TokenProvider == null) return null;

        var token = await TokenProvider.CheckGenerateToken(ct);

        if (token != null) CheckRefererHeader(token.Referer);
        return token;
    }

    void CheckRefererHeader(string referrer)
    {
        if (_httpClient == null || string.IsNullOrWhiteSpace(referrer)) return;

        Uri referer;
        bool validReferrerUrl = Uri.TryCreate(referrer, UriKind.Absolute, out referer);
        if (!validReferrerUrl)
            throw new HttpRequestException(string.Format("Not a valid url for referrer: {0}", referrer));
        _httpClient.DefaultRequestHeaders.Referrer = referer;
    }

    public Task<AddAttachmentResponse> AddAttachment(AttachmentToPost attachment)
    {
        return AddAttachment(attachment, CancellationToken.None);
    }

    public Task<AddAttachmentResponse> AddAttachment(AttachmentToPost attachment, CancellationToken ct)
    {
        Guard.AgainstNullArgument("attachment", attachment);
        Guard.AgainstNullArgumentProperty("attachment", "Attachment", attachment.Attachment);

        return Post<AddAttachmentResponse, AttachmentToPost>(attachment, ct);
    }

    protected async Task<T> Post<T, TRequest>(TRequest requestObject, CancellationToken ct)
        where TRequest : CommonParameters, IAttachment, IEndpoint
        where T : IPortalResponse
    {
        var endpoint = requestObject;
        var url = endpoint.BuildAbsoluteUrl(RootUrl).Split('?').FirstOrDefault();

        var token = await CheckGenerateToken(ct);
        if (ct.IsCancellationRequested) return default(T);

        // these should have already been added
        if (string.IsNullOrWhiteSpace(requestObject.Token) && token != null && !string.IsNullOrWhiteSpace(token.Value))
        {
            requestObject.Token = token.Value;
            _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(token.Value);
            if (token.AlwaysUseSsl) url = url.Replace("http:", "https:");
        }

        _httpClient.CancelPendingRequests();

        Uri uri;
        bool validUrl = Uri.TryCreate(url, UriKind.Absolute, out uri);
        if (!validUrl)
            throw new HttpRequestException(string.Format("Not a valid url: {0}", url));

        System.Diagnostics.Debug.WriteLine(uri);
        string resultString = string.Empty;
        var attachment = (IAttachment)requestObject;

        using (var content = new MultipartFormDataContent())
        {
            var streamContent = new StreamContent(new MemoryStream(attachment.Attachment));
            var header = new ContentDispositionHeaderValue("form-data");
            header.Name = "\"attachment\"";
            header.FileName = attachment.FileName;
            streamContent.Headers.ContentDisposition = header;
            streamContent.Headers.ContentType = new MediaTypeHeaderValue(attachment.ContentType);

            content.Add(streamContent);
            content.Add(new StringContent("json"), "f");
            if (!string.IsNullOrWhiteSpace(requestObject.Token)) content.Add(new StringContent(requestObject.Token), "token");

            try
            {
                using (var message = await _httpClient.PostAsync(uri, content))
                {
                    message.EnsureSuccessStatusCode();
                    resultString = await message.Content.ReadAsStringAsync();
                }
            }
            catch (TaskCanceledException)
            {
                return default(T);
            }
        }

        var result = Serializer.AsPortalResponse<T>(resultString);
        if (result.Error != null) throw new InvalidOperationException(result.Error.ToString());

        return result;
    }
}