Use JS library in Flutter - dart

I am trying to use Ether JS in my Flutter application. I know that it is not directly supported and even the existing implementations are not really well documented.
Is there any way I can use this library in my Flutter application for Android and iOS? Any other alternative suggestion is also welcome.
I have tried js.dart but could not figure out how to use it. I am not even sure if it is the right choice for this scenario.
I have also tried Flutter WebView Plugin.
plugin.evalJavascript(
'function add(a,b){return a+b;}add(2,3);'
).then((s) {
print(s);
}
This function rightly returns 5 as the response. But I do not understand how to use the EtherJS library like this.
I am a noob with Flutter, Dart and JS. Any help will be appreciated.

I eventually solved this by using Platform channels as suggested by rmtmckenzie in this answer.
I downloaded the JS file and saved it to android/app/src/main/res/raw/ether.js and ios/runner/ether.js for Android and iOS respectively.
Installing dependencies
Android
Add LiquidCore as a dependency in app level build.gradle
implementation 'com.github.LiquidPlayer:LiquidCore:0.5.0'
iOS
For iOS I used the JavaScriptCore which is part of the SDK.
Platform Channel
In my case, I needed to create a Wallet based on a Mnemonic (look up BIP39) I pass in into the JS function. For this, I created a Platform channel which passes in the Mnemonic (which is basically of type String) as an argument and will return a JSON object when done.
Future<dynamic> getWalletFromMnemonic({#required String mnemonic}) {
return platform.invokeMethod('getWalletFromMnemonic', [mnemonic]);
}
Android Implementation (Java)
Inside MainActivity.java add this after this line
GeneratedPluginRegistrant.registerWith(this);
String CHANNEL = "UNIQUE_CHANNEL_NAME";
new MethodChannel(getFlutterView(), CHANNEL).setMethodCallHandler(
new MethodChannel.MethodCallHandler() {
#Override
public void onMethodCall(MethodCall methodCall, MethodChannel.Result result) {
if (methodCall.method.equals("getWalletFromMnemonic")) {
ArrayList<Object> args = (ArrayList<Object>) methodCall.arguments;
String mnemonic = (String) args.get(0);
JSObject walletFromMnemonic = getWalletFromMnemonic(mnemonic);
if (walletFromMnemonic == null) {
result.error("Could not create", "Wallet generation failed", null);
return;
}
String privateKey = walletFromMnemonic.property("privateKey").toString();
String address = walletFromMnemonic.property("address").toString();
HashMap<String, String> map = new HashMap<>();
map.put("privateKey", privateKey);
map.put("address", address);
JSONObject obj = new JSONObject(map);
result.success(obj.toString());
} else {
result.notImplemented();
}
}
}
);
Declare the following methods which perform the actual action of interacting with the JS library and returning the result to the platform channel.
#Nullable
#VisibleForTesting
private JSObject getWalletFromMnemonic(String mnemonic) {
JSContext jsContext = getJsContext(getEther());
JSObject wallet = getWalletObject(jsContext);
if (wallet == null) {
return null;
}
if (!wallet.hasProperty("fromMnemonic")) {
return null;
}
JSFunction walletFunction = wallet.property("fromMnemonic").toObject().toFunction();
return walletFunction.call(null, mnemonic).toObject();
}
#Nullable
#VisibleForTesting
private JSObject getWalletObject(JSContext context) {
JSObject jsEthers = context.property("ethers").toObject();
if (jsEthers.hasProperty("Wallet")) {
return jsEthers.property("Wallet").toObject();
}
return null;
}
#VisibleForTesting
String getEther() {
String s = "";
InputStream is = getResources().openRawResource(R.raw.ether);
try {
s = IOUtils.toString(is);
} catch (IOException e) {
s = null;
e.printStackTrace();
} finally {
IOUtils.closeQuietly(is);
}
return s;
}
#VisibleForTesting
JSContext getJsContext(String code) {
JSContext context = new JSContext();
context.evaluateScript(code);
return context;
}
iOS Implementation (Swift)
Add the following lines in AppDelegate.swift inside the override application method.
final let methodChannelName: String = "UNIQUE_CHANNEL_NAME"
let controller: FlutterViewController = window?.rootViewController as! FlutterViewController
let methodChannel = FlutterMethodChannel.init(name: methodChannelName, binaryMessenger: controller)
methodChannel.setMethodCallHandler({
(call: FlutterMethodCall, result: FlutterResult)-> Void in
if call.method == "getWalletFromMnemonic" {
guard let mnemonic = call.arguments as? [String] else {
return
}
if let wallet = self.getWalletFromMnemonic(mnemonic: mnemonic[0]) {
result(wallet)
} else {
result("Invalid")
}
}
})
Add the logic to interact with the JavaScriptCore.
private func getWalletFromMnemonic(mnemonic: String) -> Dictionary<String, String>? {
let PRIVATE_KEY = "privateKey"
let ADDRESS = "address"
guard let jsContext = self.initialiseJS(jsFileName: "ether") else { return nil }
guard let etherObject = jsContext.objectForKeyedSubscript("ethers") else { return nil }
guard let walletObject = etherObject.objectForKeyedSubscript("Wallet") else { return nil }
guard let walletFromMnemonicObject = walletObject.objectForKeyedSubscript("fromMnemonic") else {
return nil
}
guard let wallet = walletFromMnemonicObject.call(withArguments: [mnemonic]) else { return nil }
guard let privateKey = wallet.forProperty(PRIVATE_KEY)?.toString() else { return nil }
guard let address = wallet.forProperty(ADDRESS)?.toString() else { return nil }
var walletDictionary = Dictionary<String, String>()
walletDictionary[ADDRESS] = address
walletDictionary[PRIVATE_KEY] = privateKey
return walletDictionary
}
private func initialiseJS(jsFileName: String) -> JSContext? {
let jsContext = JSContext()
guard let jsSourcePath = Bundle.main.path(forResource: jsFileName, ofType: "js") else {
return nil
}
do {
let jsSourceContents = try String(contentsOfFile: jsSourcePath)
jsContext!.evaluateScript(jsSourceContents)
return jsContext!
} catch {
print(error.localizedDescription)
}
return nil
}

Honestly, if you're new to Flutter, Dart, and JS you are going to have some trouble with this unless you're willing to invest a fair amount of time. It does depend on what exactly you're trying to make with the Ether JS library, but in general you're going to have a hard time integrating it with flutter. There is an Ethereum package but it seems much narrower in scope than the ether.js library you've been looking at - it mostly seems focused on communication with the RPC api rather than dealing with wallets etc.
If you're dead set on using Flutter, your best bet would be to use Android & iOS specific libraries to do the actual ethereum stuff and to communicate through Platform Channels to a common api in your dart code. This could be a significant undertaking depending on how much of the API you need to expose, especially for someone new to flutter/dart and possibly to android/ios development as well. This will be much more performant than communicating back and forth with javascript running in a webview though, and realistically probably easier to code as well because flutter doesn't really have any good mechanisms for calling js code right now.
There is another option - to use a different client UI framework entirely. React native might do everything you need and has the advantage of being in Javascript so it can most likely integrate the Ether.js library easily, although I can't guarantee that it will actually fully support the ether.js library (its runtime might not have the necessary crypto extensions for example).

Future<String> loadJS(String name) async {
var givenJS = rootBundle.loadString('assets/$name.js');
return givenJS.then((String js) {
flutterWebViewPlugin.onStateChanged.listen((viewState) async {
if (viewState.type == WebViewState.finishLoad) {
flutterWebViewPlugin.evalJavascript(js);
}
});
});
}

Related

Android dataStore with flow not get update after edit

I'm use DataStore with flow but I cant get any update on the flow when editing DataStore.
Store.kt
private class IStore(private val context: Context): Store {
val eventIDKey = stringPreferencesKey("EventID")
override suspend fun setEventID(eventID: String) {
context.dataStoreSettings.edit { settings ->
settings[eventIDKey] = eventID
}
}
override fun getEventID(): Flow<String> {
return context.dataStoreSettings.data.map { settings -> settings[eventIDKey].orEmpty() }
}
}
and manipulate getEventID() with data from room database in event service
EventService.kt
fun getSelectedEventLive() = store.getEventID()
.onEach { Log.d("EventService", "income new event id $it") }
.flatMapConcat { if(it.isNotBlank()) eventDao.get(it) else flowOf(null) }
onEach called when I collect the data but when updated it's not called again and need to close and open the app to show the latest data
MainViewModel.kt
val selectedEvent = eventService.getSelectedEventLive()
.stateIn(viewModelScope, SharingStarted.Lazily, null)
and use on Compose with this
val currentEvent by mainViewModel.selectedEvent.collectAsState()
Maybe I doing wrong or maybe there is something I miss?
Usually, you want to use flow.collect {...}, since Flow is cold and need to know that it is being collected to start producing new values.
// MainViewModel.kt
private val _selectedEvent = MutableStateFlow<TypeOfYourEvent>()
val selectedEvent: StateFlow<TypeOfYourEvent> = _selectedEvent
init {
viewModelScope.launch {
getSelectedEventLive().collect { it ->
_selectedEvent.value = it
}
}
}
This example should be fine with your composable's code, you still can collect selectedEvent as state.
Yeah i found the solusion its works if i change the flatMapConcat with flatMapLatest in EventService.kt
fun getSelectedEventLive() = store.getEventID()
.filterNot { it.isBlank() }
.flatMapLatest { eventDao.get(it) }

How fix this null check issue?

Dart Language:
This is plugin issue
xmpp_stone plugin
I can't fix this issue
anyone known please replay
[![enter image description here]
https://i.stack.imgur.com/FHe0O.png
Dart Language:
This is plugin issue
xmpp_stone plugin
I can't fix this issue
anyone known please replay
[![enter image description here]
https://i.stack.imgur.com/FHe0O.png
This is full code issue in xmpp_stone plugin
import 'dart:io';
import 'package:xmpp_stone/src/logger/Log.dart';
import 'package:console/console.dart';
import 'dart:async';
import 'dart:convert';
import 'package:xmpp_stone/xmpp_stone.dart' as xmpp;
import 'package:image/image.dart' as image;
final String TAG = 'example';
class ExampleConnectionStateChangedListener implements xmpp.ConnectionStateChangedListener {
late xmpp.Connection _connection;
late xmpp.MessagesListener _messagesListener;
StreamSubscription<String>? subscription;
ExampleConnectionStateChangedListener(xmpp.Connection connection, xmpp.MessagesListener messagesListener) {
_connection = connection;
_messagesListener = messagesListener;
_connection.connectionStateStream.listen(onConnectionStateChanged);
}
#override
void onConnectionStateChanged(xmpp.XmppConnectionState state) {
if (state == xmpp.XmppConnectionState.Ready) {
Log.d(TAG, 'Connected');
_connection.getMamModule().queryAll();
var vCardManager = xmpp.VCardManager(_connection);
vCardManager.getSelfVCard().then((vCard) {
if (vCard != null) {
Log.d(TAG, 'Your info' + vCard.buildXmlString());
}
});
var messageHandler = xmpp.MessageHandler.getInstance(_connection);
var rosterManager = xmpp.RosterManager.getInstance(_connection);
messageHandler.messagesStream.listen(_messagesListener.onNewMessage);
sleep(const Duration(seconds: 1));
var receiver = 'yyy#gmail.com';
var receiverJid = xmpp.Jid.fromFullJid(receiver);
rosterManager.addRosterItem(xmpp.Buddy(receiverJid)).then((result) {
if (result.description != null) {
print("TAG, 'add roster'" + result.description!);
}
});
sleep(const Duration(seconds: 1));
vCardManager.getVCardFor(receiverJid).then((vCard) {
if (vCard != null) {
print("TAG, 'Receiver info'" + vCard.buildXmlString());
if (vCard != null && vCard.image != null) {
var file = File('test456789.jpg')..writeAsBytesSync(image.encodeJpg(vCard.image!));
print("TAG, IMAGE SAVED TO: ${file.path}");
}
}
});
var presenceManager = xmpp.PresenceManager.getInstance(_connection);
presenceManager.presenceStream.listen(onPresence);
}
}
void onPresence(xmpp.PresenceData event) {
Log.d(TAG, 'presence Event from ' + event.jid!.fullJid! + ' PRESENCE: ' + event.showElement.toString());
}
}
Stream<String> getConsoleStream() {
return Console.adapter.byteStream().map((bytes) {
var str = ascii.decode(bytes);
str = str.substring(0, str.length - 1);
return str;
});
}
class ExampleMessagesListener implements xmpp.MessagesListener {
#override
void onNewMessage(xmpp.MessageStanza? message) {
if (message!.body != null) {
Log.d(TAG ,format(
'New Message from {color.blue}${message.fromJid!.userAtDomain}{color.end} message: {color.red}${message.body}{color.end}'));
}
}
#override
void onChatMessage(xmpp.MessageStanza? message) {
print(message);
if (message!.body != null) {
Log.d(TAG,format(
'New Message from {color.blue}${message.fromJid!.userAtDomain}{color.end} message: {color.red}${message.body}{color.end}'));
}
}
}
sendmessageforxmpp(){
var userAtDomain = 'xxx#gmail.com';
var password = 'password';
var jid = xmpp.Jid.fromFullJid(userAtDomain);
var account = xmpp.XmppAccountSettings(userAtDomain, jid.local,
jid.domain, password, 5222, resource: 'xmppstone');
var connection = xmpp.Connection(account);
var receiver = 'yyy#gmail.com';
var receiverJid = xmpp.Jid.fromFullJid(receiver);
Log.d(TAG, receiverJid.fullJid.toString());
var messageHandler =
xmpp.MessageHandler.getInstance(connection);
messageHandler.sendMessage(receiverJid, "str");
}
Your problem is that you are not using xmpp_stone correctly and therefore ends up in a situation where the internal state of xmpp_stone does not match what the developer of the package have intended.
I do, however, think the package are badly designed in such a way that wrong usage are very likely to happen so I would not blame you for getting into trouble.
The problem is the following in your code:
var connection = xmpp.Connection(account);
// ..
var messageHandler = xmpp.MessageHandler.getInstance(connection);
messageHandler.sendMessage(receiverJid, "str");
You are here creating a Connection but the underlying socket are never created. The default value for the internal state of Connection are XmppConnectionState.Idle. But when you are later trying to sendMessage, your code ends up running this from the package:
void write(message) {
Log.xmppp_sending(message);
if (isOpened()) {
_socket!.write(message);
}
}
bool isOpened() {
return state != XmppConnectionState.Closed &&
state != XmppConnectionState.ForcefullyClosed &&
state != XmppConnectionState.Closing &&
state != XmppConnectionState.SocketOpening;
}
The isOpened() ends up returning true since it sees XmppConnectionState.Idle as an open state where messages are allowed to be sent.
But that is not the case here since we never asked Connection to open actually do any connection and therefore _socket ends up being null. Since the package are trying to do ! on null, the application crashes.
For an actual solution, we can get inspired from the example implementation from xmpp_dart:
https://github.com/vukoye/xmpp_dart/blob/master/example/example.dart
We can here see they have a connection.connect(); call. But, I am going to guess this really only works because the example are not going to use the connection right after this call. The problem is that it is implemented like the following:
void connect() {
if (_state == XmppConnectionState.Closing) {
_state = XmppConnectionState.WouldLikeToOpen;
}
if (_state == XmppConnectionState.Closed) {
_state = XmppConnectionState.Idle;
}
if (_state == XmppConnectionState.Idle) {
openSocket();
}
}
Future<void> openSocket() async {
connectionNegotatiorManager.init();
setState(XmppConnectionState.SocketOpening);
try {
return await Socket.connect(account.host ?? account.domain, account.port)
.then((Socket socket) {
// if not closed in meantime
if (_state != XmppConnectionState.Closed) {
setState(XmppConnectionState.SocketOpened);
So connect() returns void but calls openSocket() which does return a Future that would be able to tell us when the connection are actually ready.
I would therefore instead suggest using openSocket() directly and make your sendmessageforxmpp() method async so we can await on the connection being open.
So your code should look like:
Future<void> sendmessageforxmpp() async {
var userAtDomain = 'xxx#gmail.com';
var password = 'password';
var jid = xmpp.Jid.fromFullJid(userAtDomain);
var account = xmpp.XmppAccountSettings(
userAtDomain, jid.local, jid.domain, password, 5222,
resource: 'xmppstone');
var connection = xmpp.Connection(account);
await connection.openSocket(); // <--- the important change :)
var receiver = 'yyy#gmail.com';
var receiverJid = xmpp.Jid.fromFullJid(receiver);
Log.d(TAG, receiverJid.fullJid.toString());
var messageHandler = xmpp.MessageHandler.getInstance(connection);
messageHandler.sendMessage(receiverJid, "str");
}
This error is usually occurring when you use the bang operator (!) on a nullable value that was not properly initialized, like
yourvariable!.somefield
The above assumes that yourvariable will not be null as this point. If it's null, then reality is in conflict with the assumption I have just described.

How to implement Apple Pay with Stripe in Xamarin forms

I am trying to implement Stripe in Xamarin forms with Apply Pay
I am following the following tutorial but I am stuck at one problem I can't find the STPApplePayContext class in Xamarin
The Library I am using
IOS Bindings or Xamarin.Stripe.iOS
Both provide iOS binidngs for Stripe.
Now as I am following the tutorial I can't find the STPApplePayContext in any of those Libs or something similar.
My code with iOS Bindings
public Task<bool> InitializeAsync(string clientToken)
{
// STPPaymentConfiguration
StripeSdk.STPPaymentConfiguration.SharedConfiguration().PublishableKey =
"pk_test_123456";
_canPay = Stripe_ApplePay.DeviceSupportsApplePay;
return Task.FromResult(true);
}
public bool CreatePaymentRequest(double amount)
{
_paymentRequest = _stripe.PaymentRequestWithMerchantIdentifier(_merchantID,"US","USD");
_paymentRequest.SupportedNetworks = _paymentNetworks;
_paymentRequest.MerchantCapabilities = PKMerchantCapability.ThreeDS;
_paymentRequest.PaymentSummaryItems = new[]
{
new PKPaymentSummaryItem()
{
Label = "Item",
Amount = new NSDecimalNumber(amount)
}
};
return true;
}
public bool Pay()
{
// var applePayContext = STPApplePayContext;
// if (applePayContext)
// {
// }
// if let applePayContext = STPApplePayContext(paymentRequest: paymentRequest, delegate: self) {
// // Present Apple Pay payment sheet
// applePayContext.presentApplePay(on: self)
// } else {
// // There is a problem with your Apple Pay configuration
// }
}
Can anybody point me in the right direction?

Large File upload to ASP.NET Core 3.0 Web API fails due to Request Body to Large

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.

Common way to execute a stored proc from both ColdFusion and Railo

I think I've gotten the most simplest scenario built. I just want to pass it by everyone for a sanity check. Here's the idea:
GetErrorCodes.cfm does the following:
<cfscript>
response = new ErrorCodes().WhereXXX(); // ACF or Railo, doesn't matter
</cfscript>
ErrorCodes.cfc:
function WhereXXX() {
return new sproc().exec('app.GetErrorCodes'); // All my functions will do this instead of executing the sproc themselves.
}
sproc.cfc:
component {
function exec(procedure) {
local.result = {};
if (server.ColdFusion.productname == 'Railo') {
return new Railo().exec(arguments.procedure); // Has to be outside of sproc.cfc because ColdFusion throws a syntax error otherwise.
}
local.svc = new storedProc();
local.svc.setProcedure(arguments.procedure);
local.svc.addProcResult(name='qry');
try {
local.obj = local.svc.execute();
local.result.Prefix = local.obj.getPrefix();
local.result.qry = local.obj.getProcResultSets().qry;
} catch(any Exception) {
request.msg = Exception.Detail;
}
return local.result;
}
Railo.cfc:
component {
function exec(procedure) {
local.result = {};
try {
storedproc procedure=arguments.procedure result="local.result.Prefix" returncode="yes" {
procresult name="local.result.qry";
}
} catch(any Exception) {
request.msg = Exception.Message;
}
return local.result;
}
}
So I've been working on this all day, but tell me, is this a sane way to keep the source code the same if it's to be run on either a ColdFusion server or a Railo server?
Um... just use <cfstoredproc> instead of trying to use two different CFScript approaches that are mutually exclusive to each other of the CFML platforms.

Resources