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.
When I try to load my application in Vaadin 14 I see a blank screen like this...
It appears that the servlet container is in fact running but it cannot host some static resources...
When I look in the network tab in the Chrome inspector I see it cannot find a few files such as
http://localhost:8080/VAADIN/static/client/client-122CE29AC0B9685B4DC485343E774096.cache.js
I am unsure why this would be. Any ideas?
UPDATE:
It appears I have to manually ensure all of my .jar files get added as resources in jetty so the static content can be served. I tried doing this like so and it works fine on my laptop but not on the server...
public class App {
public static void main(String[] args) throws Exception {
final var server = new Server(8080);
// Specifies the order in which the configurations are scanned.
Configuration.ClassList classlist = Configuration.ClassList.setServerDefault(server);
classlist.addAfter("org.eclipse.jetty.webapp.FragmentConfiguration", "org.eclipse.jetty.plus.webapp.EnvConfiguration", "org.eclipse.jetty.plus.webapp.PlusConfiguration");
classlist.addBefore("org.eclipse.jetty.webapp.JettyWebXmlConfiguration", "org.eclipse.jetty.annotations.AnnotationConfiguration");
// Creation of a temporal directory.
File tempDir = new File(System.getProperty("java.io.tmpdir"), "JettyTest");
if (tempDir.exists()) {
if (!tempDir.isDirectory()) {
throw new RuntimeException("Not a directory: " + tempDir);
}
} else if (!tempDir.mkdirs()) {
throw new RuntimeException("Could not make: " + tempDir);
}
WebAppContext context = new WebAppContext();
context.setInitParameter("productionMode", "false");
// Context path of the application.
context.setContextPath("");
// Exploded war or not.
context.setExtractWAR(false);
context.setTempDirectory(tempDir);
// It pulls the respective config from the VaadinServlet.
context.addServlet(GuiceVaadinServlet.class, "/*").setInitOrder(1);
context.setAttribute("org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern", ".*");
context.setParentLoaderPriority(true);
server.setHandler(context);
// This add jars to the jetty classpath in a certain syntax and the pattern makes sure to load all of them.
final var classpathEntries = ClassPathHelper.getAllClassPathEntries();
final var ideMode = classpathEntries.size() > 1;
final var resourceList = new ArrayList<Resource>();
final var jarFiles = new ArrayList<File>();
if (ideMode) {
System.out.println("Starting in IDE Mode");
for (String entry : ClassPathHelper.getAllClassPathEntries()) {
if (entry.endsWith(".jar")) {
final var file = new File(entry);
jarFiles.add(file);
}
}
} else {
final var baseInstallDir = System.getProperty("user.dir");
System.out.println("Starting in Server WebJar Mode");
final var libsDirectory = new File(baseInstallDir, "lib");
System.out.println("Scanning for jars in " + libsDirectory.getPath());
for (File file : Objects.requireNonNull(libsDirectory.listFiles())) {
if (file.getPath().endsWith(".jar")) {
jarFiles.add(file);
}
}
final var sferionJar = new File(baseInstallDir, "sferion.jar");
jarFiles.add(sferionJar);
System.out.println("Found " + jarFiles.size() + " jar files");
}
for (File jarFile : jarFiles) {
resourceList.add(Resource.newResource("jar:" + jarFile.toURI().toURL() + "!/"));
}
if (ideMode) {
// It adds the web application resources. Styles, client-side components, ...
//TODO: make this property dynamic somehow?
resourceList.add(Resource.newResource("/usr/local/code/sferion/planglobal/src/main/webapp"));
}
// The base resource is where jetty serves its static content from.
context.setBaseResource(new ResourceCollection(resourceList.toArray(new Resource[0])));
server.start();
server.join();
}
}
#Bean
public Server rsServer2() {
JAXRSServerFactoryBean endpoint = new JAXRSServerFactoryBean();
endpoint.setProvider(new JacksonJsonProvider());
endpoint.setBus(cxf());
endpoint.setAddress("/rservice2");
endpoint.setServiceBeans(Arrays.<Object>asList(someService3));
endpoint.setFeatures(Arrays.asList(swagger2Feature2()));
return endpoint.create();
}
#Bean
public Server rsServer() {
JAXRSServerFactoryBean endpoint = new JAXRSServerFactoryBean();
endpoint.setProvider(new JacksonJsonProvider());
endpoint.setBus(cxf());
endpoint.setAddress("/rservice");
endpoint.setServiceBeans(Arrays.<Object>asList(someService2));
endpoint.setFeatures(Arrays.asList(swagger2Feature()));
return endpoint.create();
}
#Bean("swagger2Feature")
Swagger2Feature swagger2Feature() {
System.out.println("Swagger2Feature Called");
Swagger2Feature swagger2Feature = new Swagger2Feature();
swagger2Feature.setResourcePackage("com.service2");
swagger2Feature.setPrettyPrint(true);
return swagger2Feature;
}
#Bean("swagger2Feature2")
Swagger2Feature swagger2Feature2() {
System.out.println("Swagger2Feature2 Called");
Swagger2Feature swagger2Feature2 = new Swagger2Feature();
swagger2Feature2.setResourcePackage("com.service3");
swagger2Feature2.setPrettyPrint(true);
return swagger2Feature2;
}
//
When hitting /basepath/rservice/services in browser. Getting below api listed.
/basepath/rservice2/api-docs?url=/basepath/rservice2/swagger.json
/basepath/rservice/api-docs?url=/basepath/rservice2/swagger.json
Both of the links are giving services listed only in
swagger2Feature.setResourcePackage("com.service2").
Desired result : both links should give their individual api's listed in their corresponding swagger2Feature.setResourcePackage.
I need to add some tag in a app.config file for implement a dll (xmlsoccer).
I have to add something like
<system.serviceModel>
<bindings>
<basicHttpBinding>
in a configuration node, but I don't know where it is.
I tried to create an app.config file and set DotNetConfig.xsd as scheme, but during compile, I have this errors:
WARNING: failed to load endpoint configuration for *
SyStem.InvalidOperationException: A Binding must be configured for this channel factory
can anyone help me?
I tried to write this:
` public class MainActivity : FormsApplicationActivity
{
public static readonly EndpointAddress EndPoint = new EndpointAddress("http://www.xmlsoccer.com/FootballDataDemo.asmx");
App application;
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
global::Xamarin.Forms.Forms.Init(this, bundle);
// LoadApplication(new App(binding, EndPoint));
CreateBasicHttp();
LoadApplication(application);
}
private void CreateBasicHttp()
{
var binding = new BasicHttpBinding()
{
Name = "basicHttpBinding",
MaxReceivedMessageSize = 1000000,
};
binding.ReaderQuotas = new System.Xml.XmlDictionaryReaderQuotas()
{
MaxArrayLength = 2147483646,
MaxStringContentLength = 5242880,
};
var timeout = new TimeSpan(0, 1, 0);
binding.SendTimeout = timeout;
binding.OpenTimeout = timeout;
binding.ReceiveTimeout = timeout;
application = new App(binding, new EndpointAddress("http://www.xmlsoccer.com/FootballDataDemo.asmx"));
}`
in my MainActivity.cs, but obviously it isn't enough.
Still looking for answer?
Take a look here:
https://forums.xamarin.com/discussion/19303/how-to-consume-wcf-service-in-xamarin-forms-pcl
Steps:
1.- Opening a command prompt in Windows and using the SLSvcUtil.exe tool
from the Silverlight SDK to generate a proxy from a WSDL file. slsvcutil http://www.yourserver.com/WebServices/YourServiceSoapClient.asmx?WSDL /out:YourService.cs That utility is located at C:\Program Files (x86)\Microsoft SDKs\Silverlight\v5.0\Tools\ on my computer.
2.- Adding the resulting YourService.cs file to my project.
3.- Adding the following code to access the service:
// Create the WCF client (created using SLSvcUtil.exe on Windows)
YourServiceSoapClient client = new YourServiceSoapClient(
new BasicHttpBinding(),
new EndpointAddress("hhttp://www.yourserver.com/WebServices/YourServiceSoapClient.asmx"));
// Call the proxy - this should use the async versions
client.ServiceFunctionCompleted += OnGotResult;
client.ServiceFunctionAsync(parameter);
And the OnGotResult function:
void OnGotResult(object sender, ServiceFunctionCompletedEventArgs e)
{
Device.BeginInvokeOnMainThread(async () => {
string error = null;
if (e.Error != null)
error = e.Error.Message;
else if (e.Cancelled)
error = "Cancelled";
if (!string.IsNullOrEmpty(error))
{
await DisplayAlert("Error", error, "OK", "Cancel");
}
else
{
resultsLabel.Text = e.Result;
}
});
}
As I am running the code in localhost mode and I have localhost:8080/url/hello. Is the value after / is the directory or the path Because I am getting the result as HTTP STATUS 404.
public void onValueChange(ValueChangeEvent<String> event)
{
String a=Window.Location.getHref();
Window.alert(a);
if(Window.Location.getHash().equals("") || Window.Location.getHash().equals(null) || Window.Location.getHash()== null)
{
String SUBURL=a.substring(a.lastIndexOf("/")+1;
String a1=SUBURL;
Window.alert("LINK :: "+a1);
if(!a1.isEmpty())
{
greetingService.shrturl(a1,new AsyncCallback<String>()
{
#Override
public void onFailure(Throwable caught)
{
Window.alert("fail");
}
#Override
public void onSuccess(String h)
{
System.out.print("return value :: "+h);
if(h.equals(null))
{
Window.Location.replace("ERROR:PAGE NOT FOUND");
}
else
{
Window.Location.replace(h);
}
}
});
}
else
{
new shorturl();
}
}
Localhost: is telling the server to loop back on itself, so instead of sending a HTTP request to a forgien server to send it to a local server,
8080:, refers to the port number,
/url/hello/ refers to the directory structure within the local server
so yes if you are migrating your code from local to live then
http://localhost:8080/url/hello
will become
http://www.mywebsite.ie/url/hello