Large File upload to ASP.NET Core 3.0 Web API fails due to Request Body to Large - asp.net-mvc

I have an ASP.NET Core 3.0 Web API endpoint that I have set up to allow me to post large audio files. I have followed the following directions from MS docs to set up the endpoint.
https://learn.microsoft.com/en-us/aspnet/core/mvc/models/file-uploads?view=aspnetcore-3.0#kestrel-maximum-request-body-size
When an audio file is uploaded to the endpoint, it is streamed to an Azure Blob Storage container.
My code works as expected locally.
When I push it to my production server in Azure App Service on Linux, the code does not work and errors with
Unhandled exception in request pipeline: System.Net.Http.HttpRequestException: An error occurred while sending the request. ---> Microsoft.AspNetCore.Server.Kestrel.Core.BadHttpRequestException: Request body too large.
Per advice from the above article, I have configured incrementally updated Kesterl with the following:
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseKestrel((ctx, options) =>
{
var config = ctx.Configuration;
options.Limits.MaxRequestBodySize = 6000000000;
options.Limits.MinRequestBodyDataRate =
new MinDataRate(bytesPerSecond: 100,
gracePeriod: TimeSpan.FromSeconds(10));
options.Limits.MinResponseDataRate =
new MinDataRate(bytesPerSecond: 100,
gracePeriod: TimeSpan.FromSeconds(10));
options.Limits.RequestHeadersTimeout =
TimeSpan.FromMinutes(2);
}).UseStartup<Startup>();
Also configured FormOptions to accept files up to 6000000000
services.Configure<FormOptions>(options =>
{
options.MultipartBodyLengthLimit = 6000000000;
});
And also set up the API controller with the following attributes, per advice from the article
[HttpPost("audio", Name="UploadAudio")]
[DisableFormValueModelBinding]
[GenerateAntiforgeryTokenCookie]
[RequestSizeLimit(6000000000)]
[RequestFormLimits(MultipartBodyLengthLimit = 6000000000)]
Finally, here is the action itself. This giant block of code is not indicative of how I want the code to be written but I have merged it into one method as part of the debugging exercise.
public async Task<IActionResult> Audio()
{
if (!MultipartRequestHelper.IsMultipartContentType(Request.ContentType))
{
throw new ArgumentException("The media file could not be processed.");
}
string mediaId = string.Empty;
string instructorId = string.Empty;
try
{
// process file first
KeyValueAccumulator formAccumulator = new KeyValueAccumulator();
var streamedFileContent = new byte[0];
var boundary = MultipartRequestHelper.GetBoundary(
MediaTypeHeaderValue.Parse(Request.ContentType),
_defaultFormOptions.MultipartBoundaryLengthLimit
);
var reader = new MultipartReader(boundary, Request.Body);
var section = await reader.ReadNextSectionAsync();
while (section != null)
{
var hasContentDispositionHeader = ContentDispositionHeaderValue.TryParse(
section.ContentDisposition, out var contentDisposition);
if (hasContentDispositionHeader)
{
if (MultipartRequestHelper
.HasFileContentDisposition(contentDisposition))
{
streamedFileContent =
await FileHelpers.ProcessStreamedFile(section, contentDisposition,
_permittedExtensions, _fileSizeLimit);
}
else if (MultipartRequestHelper
.HasFormDataContentDisposition(contentDisposition))
{
var key = HeaderUtilities.RemoveQuotes(contentDisposition.Name).Value;
var encoding = FileHelpers.GetEncoding(section);
if (encoding == null)
{
return BadRequest($"The request could not be processed: Bad Encoding");
}
using (var streamReader = new StreamReader(
section.Body,
encoding,
detectEncodingFromByteOrderMarks: true,
bufferSize: 1024,
leaveOpen: true))
{
// The value length limit is enforced by
// MultipartBodyLengthLimit
var value = await streamReader.ReadToEndAsync();
if (string.Equals(value, "undefined",
StringComparison.OrdinalIgnoreCase))
{
value = string.Empty;
}
formAccumulator.Append(key, value);
if (formAccumulator.ValueCount >
_defaultFormOptions.ValueCountLimit)
{
return BadRequest($"The request could not be processed: Key Count limit exceeded.");
}
}
}
}
// Drain any remaining section body that hasn't been consumed and
// read the headers for the next section.
section = await reader.ReadNextSectionAsync();
}
var form = formAccumulator;
var file = streamedFileContent;
var results = form.GetResults();
instructorId = results["instructorId"];
string title = results["title"];
string firstName = results["firstName"];
string lastName = results["lastName"];
string durationInMinutes = results["durationInMinutes"];
//mediaId = await AddInstructorAudioMedia(instructorId, firstName, lastName, title, Convert.ToInt32(duration), DateTime.UtcNow, DateTime.UtcNow, file);
string fileExtension = "m4a";
// Generate Container Name - InstructorSpecific
string containerName = $"{firstName[0].ToString().ToLower()}{lastName.ToLower()}-{instructorId}";
string contentType = "audio/mp4";
FileType fileType = FileType.audio;
string authorName = $"{firstName} {lastName}";
string authorShortName = $"{firstName[0]}{lastName}";
string description = $"{authorShortName} - {title}";
long duration = (Convert.ToInt32(durationInMinutes) * 60000);
// Generate new filename
string fileName = $"{firstName[0].ToString().ToLower()}{lastName.ToLower()}-{Guid.NewGuid()}";
DateTime recordingDate = DateTime.UtcNow;
DateTime uploadDate = DateTime.UtcNow;
long blobSize = long.MinValue;
try
{
// Update file properties in storage
Dictionary<string, string> fileProperties = new Dictionary<string, string>();
fileProperties.Add("ContentType", contentType);
// update file metadata in storage
Dictionary<string, string> metadata = new Dictionary<string, string>();
metadata.Add("author", authorShortName);
metadata.Add("tite", title);
metadata.Add("description", description);
metadata.Add("duration", duration.ToString());
metadata.Add("recordingDate", recordingDate.ToString());
metadata.Add("uploadDate", uploadDate.ToString());
var fileNameWExt = $"{fileName}.{fileExtension}";
var blobContainer = await _cloudStorageService.CreateBlob(containerName, fileNameWExt, "audio");
try
{
MemoryStream fileContent = new MemoryStream(streamedFileContent);
fileContent.Position = 0;
using (fileContent)
{
await blobContainer.UploadFromStreamAsync(fileContent);
}
}
catch (StorageException e)
{
if (e.RequestInformation.HttpStatusCode == 403)
{
return BadRequest(e.Message);
}
else
{
return BadRequest(e.Message);
}
}
try
{
foreach (var key in metadata.Keys.ToList())
{
blobContainer.Metadata.Add(key, metadata[key]);
}
await blobContainer.SetMetadataAsync();
}
catch (StorageException e)
{
return BadRequest(e.Message);
}
blobSize = await StorageUtils.GetBlobSize(blobContainer);
}
catch (StorageException e)
{
return BadRequest(e.Message);
}
Media media = Media.Create(string.Empty, instructorId, authorName, fileName, fileType, fileExtension, recordingDate, uploadDate, ContentDetails.Create(title, description, duration, blobSize, 0, new List<string>()), StateDetails.Create(StatusType.STAGED, DateTime.MinValue, DateTime.UtcNow, DateTime.MaxValue), Manifest.Create(new Dictionary<string, string>()));
// upload to MongoDB
if (media != null)
{
var mapper = new Mapper(_mapperConfiguration);
var dao = mapper.Map<ContentDAO>(media);
try
{
await _db.Content.InsertOneAsync(dao);
}
catch (Exception)
{
mediaId = string.Empty;
}
mediaId = dao.Id.ToString();
}
else
{
// metadata wasn't stored, remove blob
await _cloudStorageService.DeleteBlob(containerName, fileName, "audio");
return BadRequest($"An issue occurred during media upload: rolling back storage change");
}
if (string.IsNullOrEmpty(mediaId))
{
return BadRequest($"Could not add instructor media");
}
}
catch (Exception ex)
{
return BadRequest(ex.Message);
}
var result = new { MediaId = mediaId, InstructorId = instructorId };
return Ok(result);
}
I reiterate, this all works great locally. I do not run it in IISExpress, I run it as a console app.
I submit large audio files via my SPA app and Postman and it works perfectly.
I am deploying this code to an Azure App Service on Linux (as a Basic B1).
Since the code works in my local development environment, I am at a loss of what my next steps are. I have refactored this code a few times but I suspect that it's environment related.
I cannot find anywhere that mentions that the level of App Service Plan is the culprit so before I go out spending more money I wanted to see if anyone here had encountered this challenge and could provide advice.
UPDATE: I attempted upgrading to a Production App Service Plan to see if there was an undocumented gate for incoming traffic. Upgrading didn't work either.
Thanks in advance.
-A

Currently, as of 11/2019, there is a limitation with the Azure App Service for Linux. It's CORS functionality is enabled by default and cannot be disabled AND it has a file size limitation that doesn't appear to get overridden by any of the published Kestrel configurations. The solution is to move the Web API app to a Azure App Service for Windows and it works as expected.
I am sure there is some way to get around it if you know the magic combination of configurations, server settings, and CLI commands but I need to move on with development.

Related

how to get date wise email faster using gmail API in Asp.Net MVC with OAuth Token

Here I have written code for Gmail API to fetch mail with date filter
I am able to fetch MessageId and ThreadId using the First API. On the basis of MessageId, I put that messageId parameter in a List object and I have sent this parameter in foreach loop from List to the next API to fetch email body on basis of messageID. But the process is very slow for fetching messages from Gmail
public async Task<ActionResult> DisplayEmailWithFilter (string fromDate, string toDate) {
Message messageObj = new Message ();
Example exampleObj = new Example ();
List<GmailMessage> gmailMessagesList = new List<GmailMessage> ();
GmailMessage gmailMessage = new GmailMessage ();
var responseData = "";
//dateFilter string parameter Created with Date Values
string dateFilter = "in:Inbox after:" + fromDate + " before:" + toDate;
try {
// calling Gmail API to get MessageID Details by Date Filter
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue (scheme: "Bearer",
parameter : Session["Token"].ToString ());
HttpResponseMessage responseMessage = await client.GetAsync ("https://www.googleapis.com/gmail/v1/users/me/messages?q=" + dateFilter);
if (responseMessage.IsSuccessStatusCode) {
var data = responseMessage.Content;
}
try {
responseData = responseMessage.Content.ReadAsStringAsync ().Result;
//This Json Data Converted into List Object
var msgList = JsonConvert.DeserializeObject<Root1> (responseData);
//loop for Fetching EmailMessageData by MessageID
if (msgList.resultSizeEstimate != 0) {
foreach (var msgItem in msgList.messages) {
messageObj.id = msgItem.id;
//Calling API with MessageID Parameter to fetch Respective Message Data
HttpResponseMessage responseMessageList = await client.GetAsync ("https://www.googleapis.com/gmail/v1/users/userId/messages/id?id=" + messageObj.id.ToString () + "&userId=me&format=full");
if (responseMessageList.IsSuccessStatusCode) {
var dataNew = responseMessageList.Content;
var responseDataNew = responseMessageList.Content.ReadAsStringAsync ().Result;
//Converting json string in Object
exampleObj = JsonConvert.DeserializeObject<Example> (responseDataNew);
gmailMessage.Body = exampleObj.snippet;
//fetching Header Values comparing with string to get Data
for (int i = 1; i < exampleObj.payload.headers.Count; i++) {
if (exampleObj.payload.headers[i].name.ToString () == "Date") {
gmailMessage.RecievedDate = exampleObj.payload.headers[i].value;
}
if (exampleObj.payload.headers[i].name.ToString () == "Subject") {
gmailMessage.Subject = exampleObj.payload.headers[i].value;
}
if (exampleObj.payload.headers[i].name.ToString () == "Message-ID") {
gmailMessage.SenderEmailID = exampleObj.payload.headers[i].value;
}
if (exampleObj.payload.headers[i].name.ToString () == "From") {
gmailMessage.SenderName = exampleObj.payload.headers[i].value;
}
}
//Adding This Object Values in GmailMessgage List Object
gmailMessagesList.Add (
new GmailMessage {
Body = exampleObj.snippet,
SenderEmailID = gmailMessage.SenderEmailID,
RecievedDate = gmailMessage.RecievedDate,
SenderName = gmailMessage.SenderName,
Subject = gmailMessage.Subject,
});
}
}
}
} catch (Exception e) {
string errorMgs = e.Message.ToString ();
throw;
}
} catch (Exception e) {
string errorMgs = e.Message.ToString ();
throw;
}
return View (gmailMessagesList);
}
I can fetch Gmail email datewise but it took so much time to fetch. how can I improve my code and performance faster?
The query seems like the most you can do. If you know more information about those emails, like a specific subjects or there always come from the same sender you can try to filter that too, like you would in the Gmail interface.
Other way you would be kind of out of luck. You are limited by the files retrieved from User.messages.list.
If you need to escape from the API limitations maybe trying to retrieve the message other way would be the correct way to go. Considerate creating a small code to retrieve message by the IMAP protocol. Several questions in this topic may help you:
Reading Gmail messages using Python IMAP
Reading Gmail Email in Python
How can I get an email message's text content using Python?

Object reference not set to an object while file upload in OneDrive

I am using Microsoft Graph SDK to upload file in chunks in OneDrive. I am using below code to upload the file:
try
{
GraphServiceClient graphClient = this.GetGraphServiceClient(accessToken);
string fileName = Path.GetFileName(srcFilePath);
using (var fileContentStream = System.IO.File.Open(srcFilePath, System.IO.FileMode.Open))
{
var uploadSession = await graphClient.Me.Drive.Root.ItemWithPath(fileName).CreateUploadSession().Request().PostAsync();
var maxChunkSize = 5 * 1024 * 1024;
var provider = new ChunkedUploadProvider(uploadSession, graphClient, fileContentStream, maxChunkSize);
var chunkRequests = provider.GetUploadChunkRequests();
var readBuffer = new byte[maxChunkSize];
var trackedExceptions = new List<Exception>();
Microsoft.Graph.DriveItem itemResult = null;
foreach (var request in chunkRequests)
{
var result = await provider.GetChunkRequestResponseAsync(request, readBuffer, trackedExceptions);
if (result.UploadSucceeded)
{
itemResult = result.ItemResponse;
}
}
}
}
catch (Microsoft.Graph.ServiceException e)
{
}
catch (Exception ex)
{
}
The above code works fine with normal file names. However, when I am trying to upload a file with name as Test#123.pdf, "Object reference not set to an object" exception is thrown at line var provider = new ChunkedUploadProvider(uploadSession, graphClient, fileContentStream, maxChunkSize); Please see below screenshot:
Is this a limitation of OneDrive SDK, or am I not passing the parameters correctly?
The # sign has a special meaning in a URL. Before you can use it, you'll need to URL Encode the file name: Test%23123.pdf.

Create to database using web api

I am trying to insert a new entry in my database using web api. I have two web projects: one is a UI project where all the user interaction will occur and the other is a services project which will handle all interactions with my database.
Below is my post method that will take in form data for creating a new team.
// POST: Api/Team/Create
[HttpPost]
public ActionResult Create(Team team)
{
try
{
if (ModelState.IsValid)
{
HttpEndPointContext httpEndPoint = new HttpEndPointContext()
{
AuthenticationMethod = HttpAuthenticationMethods.None,
Ssl = false,
HttpMethod = HttpMethod.Post,
Path = "localhost:32173/api/team/",
QueryStrings = null,
PayloadData = SerializationHelper.Current.Serialize(team.ToString(), SerializationTypes.Xml)
};
IProcessResult result = HttpConnectionManager.Current.SendMessage(httpEndPoint);
}
return RedirectToAction("Index");
}
catch
{
return View();
}
}
And this is my method for dealing with my PayloadStream/PayloadData attribute in the above method:
private void StreamPayload(HttpWebRequest webRequest, HttpEndPointContext httpEndPointContext)
{
if (httpEndPointContext.HttpMethod == new HttpMethod("GET"))
return;
//TODO: FIX MAYBE .... sometimes we want to post body with GET.
//Stream vs string
if (httpEndPointContext.PayloadStream == null)
{
//Wrap with SOAP Envelope and method if defined in SoapDefinition
string data = httpEndPointContext.PayloadData ?? String.Empty;
if (httpEndPointContext.SoapDefinition != null)
{
//If parameters is set, clear existing payload data.
data = String.Empty;
if (httpEndPointContext.SoapDefinition.Parameters != null)
foreach (var parameter in httpEndPointContext.SoapDefinition.Parameters)
{
data += String.Format("<{0}>{1}</{0}>", parameter.Key, parameter.Value);
}
data = String.Format("<s:Envelope xmlns:s='http://schemas.xmlsoap.org/soap/envelope/'>" +
"<s:Body><{0} xmlns='{2}'>" +
"{1}</{0}></s:Body></s:Envelope>",
httpEndPointContext.SoapDefinition.SoapMethod, data,httpEndPointContext.SoapDefinition.SoapGlobalKey);
}
byte[] byteArray = System.Text.Encoding.UTF8.GetBytes(data);
httpEndPointContext.PayloadStream = new MemoryStream(byteArray);
}
using (Stream requestStream = webRequest.GetRequestStream())
{
StreamHelper.Current.CopyStreams(httpEndPointContext.PayloadStream, requestStream);
requestStream.Close();
}
}
And the code for getting the server response. I'm currently getting an Internal Server (500) Error. Not sure why.
public IProcessResult SendMessage(HttpEndPointContext httpEndPointContext)
{
HttpWebRequest webRequest = CreateWebRequest(httpEndPointContext);
StreamPayload(webRequest, httpEndPointContext);
IProcessResult result = GetWebResponse(webRequest, httpEndPointContext);
return result;
}
private IProcessResult GetWebResponse(HttpWebRequest webRequest, HttpEndPointContext httpEndPointContext)
{
//Get Response
WebResponse response;
IProcessResult result = new ProcessResult(Statuses.Success);
try
{
response = webRequest.GetResponse();
}
catch (System.Net.WebException ex)
{
//Do exception handling. Still get the response for 500s etc.
result.Error.Exception = ex;
result.Status = Constants.Statuses.FailedUnknown;
result.ResponseCodeDescription = ex.Status.ToString();
result.ResponseCode = ex.Status.ToString();
result.Error.ErrorCode = ex.Status.ToString();
response = ex.Response;
//The error did not have any response, such as DNS lookup.
if (response == null)
return result;
}
try
{
//Get the response stream.
Stream responseData = response.GetResponseStream();
if (responseData == null)
throw new CoreException("No Response Data in GetWebResponse.",
"No Response Data in GetWebResponse. EndPoint:{0}", httpEndPointContext.ToString());
// Open the stream using a StreamReader for easy access.
var reader = new StreamReader(responseData);
// Read the content.
result.ResponseData = reader.ReadToEnd();
}
finally
{
response.Close();
}
result.ResponseCode = ((int)((HttpWebResponse)response).StatusCode).ToString();
result.ResponseCodeDescription = ((HttpWebResponse) response).StatusDescription;
return result;
}
And finally, my method for inserting to the database, found in my services project:
//POST api/controller/5
public IProcessResult Insert(Team team)
{
return TeamBusinessManager.Current.Insert(SecurityManager.Current.ConnectionContext, new Team());
}
I'm confused as to why I'm getting the 500 error. I'm not sure if it's the PayloadData attribute in my POST method or is it something wrong with my method in my services project.

Receive, send file over Web Api

I'm trying to write a WebApi service that receives a file, does a trivial manipulation, and sends the file back. I'm having issues on sending and/or receiving the file from the service.
The issue I'm having is that the file returned from the service is ~1.5x larger than the manipulated file, e.g. when the file is returned it's like 300kb instead of the 200kb it should be.
I assume its being wrapped and or manipulated somehow, and I'm unsure of how to receive it properly. The code for the WebAPI service and the method that calls the web service are included below
In, the WebApi service, when I hit the line return Ok(bufferResult), the file is a byte[253312]
In the method that calls the web service, after the file is manipulated and returned, following the line var content = stream.Result;, the stream has a length of 337754 bytes.
Web API service code
public ConversionController: APIController{
public async Task<IHttpActionResult> TransformImage()
{
if (!Request.Content.IsMimeMultipartContent())
throw new Exception();
var provider = new MultipartMemoryStreamProvider();
await Request.Content.ReadAsMultipartAsync(provider);
var file = provider.Contents.First();
var filename = file.Headers.ContentDisposition.FileName.Trim('\"');
var buffer = await file.ReadAsByteArrayAsync();
var stream = new MemoryStream(buffer);
// [file manipulations omitted;]
// [the result is populated into a MemoryStream named response ]
//debug : save memory stream to disk to make sure tranformation is successfull
/*response.Position = 0;
path = #"C:\temp\file.ext";
using (var fileStream = System.IO.File.Create(path))
{
saveStream.CopyTo(fileStream);
}*/
var bufferResult = response.GetBuffer();
return Ok(bufferResult);
}
}
Method Calling the Service
public async Task<ActionResult> AsyncConvert()
{
var url = "http://localhost:49246/api/conversion/transformImage";
var filepath = "drive/file/path.ext";
HttpContent fileContent = new ByteArrayContent(System.IO.File.ReadAllBytes(filepath));
using (var client = new HttpClient())
{
using (var formData = new MultipartFormDataContent())
{
formData.Add(fileContent, "file", "fileName");
//call service
var response = client.PostAsync(url, formData).Result;
if (!response.IsSuccessStatusCode)
{
throw new Exception();
}
else
{
if (response.Content.GetType() != typeof(System.Net.Http.StreamContent))
throw new Exception();
var stream = response.Content.ReadAsStreamAsync();
var content = stream.Result;
var path = #"drive\completed\name.ext";
using (var fileStream = System.IO.File.Create(path))
{
content.CopyTo(fileStream);
}
}
}
}
return null;
}
I'm still new to streams and WebApi, so I may be missing something quite obvious. Why are the file streams different sizes? (eg. is it wrapped and how do I unwrap and/or receive the stream)
okay, to receive the file correctly, I needed to replace the line
var stream = response.Content.ReadAsStreamAsync();
with
var contents = await response.Content.ReadAsAsync<Byte[]>();
to provide the correct type for the binding
so, the later part of the methods that calls the service looks something like
var content = await response.Content.ReadAsAsync<Byte[]>();
var saveStream = new MemoryStream(content);
saveStream.Position = 0;
//Debug: save converted file to disk
/*
var path = #"drive\completed\name.ext";
using (var fileStream = System.IO.File.Create(path))
{
saveStream.CopyTo(fileStream);
}*/

Example SNS subscription confirmation using AWS .NET SDK

I am trying to figure out how to use the AWS .NET SDK to confirm a subscription to a SNS Topic.
The subscription is via HTTP
The endpoint will be in a .net mvc website.
I can't find any .net examples anywhere?
A working example would be fantastic.
I'm trying something like this
Dim snsclient As New Amazon.SimpleNotificationService.AmazonSimpleNotificationServiceClient(ConfigurationSettings.AppSettings("AWSAccessKey"), ConfigurationSettings.AppSettings("AWSSecretKey"))
Dim TopicArn As String = "arn:aws:sns:us-east-1:991924819628:post-delivery"
If Request.Headers("x-amz-sns-message-type") = "SubscriptionConfirmation" Then
Request.InputStream.Seek(0, 0)
Dim reader As New System.IO.StreamReader(Request.InputStream)
Dim inputString As String = reader.ReadToEnd()
Dim jsSerializer As New System.Web.Script.Serialization.JavaScriptSerializer
Dim message As Dictionary(Of String, String) = jsSerializer.Deserialize(Of Dictionary(Of String, String))(inputString)
snsclient.ConfirmSubscription(New Amazon.SimpleNotificationService.Model.ConfirmSubscriptionRequest With {.AuthenticateOnUnsubscribe = False, .Token = message("Token"), .TopicArn = TopicArn})
End If
Here is a working example using MVC WebApi 2 and the latest AWS .NET SDK.
var jsonData = Request.Content.ReadAsStringAsync().Result;
var snsMessage = Amazon.SimpleNotificationService.Util.Message.ParseMessage(jsonData);
//verify the signaure using AWS method
if(!snsMessage.IsMessageSignatureValid())
throw new Exception("Invalid signature");
if(snsMessage.Type == Amazon.SimpleNotificationService.Util.Message.MESSAGE_TYPE_SUBSCRIPTION_CONFIRMATION)
{
var subscribeUrl = snsMessage.SubscribeURL;
var webClient = new WebClient();
webClient.DownloadString(subscribeUrl);
return "Successfully subscribed to: " + subscribeUrl;
}
Building on #Craig's answer above (which helped me greatly), the below is an ASP.NET MVC WebAPI controller for consuming and auto-subscribing to SNS topics. #WebHooksFTW
using RestSharp;
using System;
using System.Net;
using System.Net.Http;
using System.Reflection;
using System.Web.Http;
using System.Web.Http.Description;
namespace sb.web.Controllers.api {
[System.Web.Mvc.HandleError]
[AllowAnonymous]
[ApiExplorerSettings(IgnoreApi = true)]
public class SnsController : ApiController {
private static string className = MethodBase.GetCurrentMethod().DeclaringType.Name;
[HttpPost]
public HttpResponseMessage Post(string id = "") {
try {
var jsonData = Request.Content.ReadAsStringAsync().Result;
var sm = Amazon.SimpleNotificationService.Util.Message.ParseMessage(jsonData);
//LogIt.D(jsonData);
//LogIt.D(sm);
if (!string.IsNullOrEmpty(sm.SubscribeURL)) {
var uri = new Uri(sm.SubscribeURL);
var baseUrl = uri.GetLeftPart(System.UriPartial.Authority);
var resource = sm.SubscribeURL.Replace(baseUrl, "");
var response = new RestClient {
BaseUrl = new Uri(baseUrl),
}.Execute(new RestRequest {
Resource = resource,
Method = Method.GET,
RequestFormat = RestSharp.DataFormat.Xml
});
if (response.StatusCode != System.Net.HttpStatusCode.OK) {
//LogIt.W(response.StatusCode);
} else {
//LogIt.I(response.Content);
}
}
//read for topic: sm.TopicArn
//read for data: dynamic json = JObject.Parse(sm.MessageText);
//extract value: var s3OrigUrlSnippet = json.input.key.Value as string;
//do stuff
return Request.CreateResponse(HttpStatusCode.OK, new { });
} catch (Exception ex) {
//LogIt.E(ex);
return Request.CreateResponse(HttpStatusCode.InternalServerError, new { status = "unexpected error" });
}
}
}
}
I don't know how recently this has changed, but I've found that AWS SNS now provides a very simply method for subscribing that doesn't involve extracting urls or building requests using RESTSharp.....Here's the simplified WebApi POST method:
[HttpPost]
public HttpResponseMessage Post(string id = "")
{
try
{
var jsonData = Request.Content.ReadAsStringAsync().Result;
var sm = Amazon.SimpleNotificationService.Util.Message.ParseMessage(jsonData);
if (sm.IsSubscriptionType)
{
sm.SubscribeToTopic(); // CONFIRM THE SUBSCRIPTION
}
if (sm.IsNotificationType) // PROCESS NOTIFICATIONS
{
//read for topic: sm.TopicArn
//read for data: dynamic json = JObject.Parse(sm.MessageText);
//extract value: var s3OrigUrlSnippet = json.input.key.Value as string;
}
//do stuff
return Request.CreateResponse(HttpStatusCode.OK, new { });
}
catch (Exception ex)
{
//LogIt.E(ex);
return Request.CreateResponse(HttpStatusCode.InternalServerError, new { status = "unexpected error" });
}
}
The following example helped me work with SNS. It goes through all the steps to work with Topics. The subscribe request in this case is an email address, however that can be changed to HTTP.
Pavel's SNS Example
Documentation
I ended up getting it working using the code shown. I was having trouble capturing the exception on the development server which turned out was telling me the server's time didn't match the timestamp in the SNS message.
Once the server's time was fixed up (an Amazon server BTW), the confirmation worked.

Resources