I am doing a POC application that uses an Android client and a Mobile Azure Service app.
I managed to implement a solution that makes use of a Sql server Compact 4.0 database file in order to store some text and small images (planned maximum 300 Kb for each).
How ever, it only works for really small images (e.g. 2 Kb) that are also compressed in the Jpeg format with the lowest quality
(using this: image.Compress(Bitmap.CompressFormat.Jpeg, 0, stream);).
I do get "Microsoft.WindowsAzure.MobileServices.MobileServiceInvalidOperationException: The request could not be completed. (Bad Request)" error for bigger images when trying to save items using the application. I did not find any where in the response any exact mention about request length limit, I assumed this.
So my question would be - is Azure Storage the only way to accomplish this task ? (also, not sure of any free option using Azure Storage).
UI part with content upload (I am using Xamarin)
Choose an image to upload (into _currentImage variable):
protected override void OnActivityResult(int requestCode, Result result, Intent data)
{
base.OnActivityResult(requestCode, result, data);
if (requestCode == 1)
{
if (result == Result.Ok)
{
var selectedImage = data.Data;
var filePath = GetPathToImage(selectedImage);
var photo = BitmapFactory.DecodeFile(filePath);
_currentImage = photo;
ImagePreview.SetImageBitmap(photo);
}
}
}
Save content invoking the Azure Mobile app:
private async void UploadItemContent()
{
using (MobileServiceClient client = new MobileServiceClient(Configuration.Urls.CloudAppUrl))
{
var table = client.GetTable<Item>();
var content = ContentDescriptionEditText.Text;
var header = ContentHeaderEditText.Text;
var stream = new MemoryStream();
_currentImage.Compress(Bitmap.CompressFormat.Jpeg, 0, stream);
var bitmapData = stream.ToArray();
var item = new Item
{
Content = content,
Header = header,
Image = bitmapData,
CreationDate = DateTime.Now.ToUniversalTime()
};
try
{
await table.InsertAsync(item);
ShowSuccessStatus();
}
catch (Exception ex)
{
ShowError(ex.Message);
}
}
}
Backend
Item definition into the Azure Mobile service:
public class Item : ITableData
{
public Item()
{
}
[Key]
[TableColumn(TableColumnType.Id)]
public string Id { get; set; }
public string Header { get; set; }
public string Content { get; set; }
[Column(TypeName = "image")]
public byte[] Image { get; set; }
public DateTime? CreationDate { get; set; }
public DateTime? UpdateDate { get; set; }
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
[Index(IsClustered = true)]
[TableColumn(TableColumnType.CreatedAt)]
public DateTime? CreatedAt { get; set; }
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
[TableColumn(TableColumnType.UpdatedAt)]
public DateTime? UpdatedAt { get; set; }
[TableColumn(TableColumnType.Deleted)]
public bool Deleted { get; set; }
[TableColumn(TableColumnType.Version)]
[Timestamp]
public byte[] Version { get; set; }
[NotMapped]
DateTimeOffset? ITableData.CreatedAt
{
get
{
throw new NotImplementedException();
}
set
{
throw new NotImplementedException();
}
}
[NotMapped]
DateTimeOffset? ITableData.UpdatedAt
{
get
{
throw new NotImplementedException();
}
set
{
throw new NotImplementedException();
}
}
}
ItemController into the Azure Mobile service:
public class ItemController : TableController<Item>
{
protected override void Initialize(HttpControllerContext controllerContext)
{
base.Initialize(controllerContext);
MobileServiceContext context = new MobileServiceContext();
DomainManager = new EntityDomainManager<Item>(context, Request);
}
...
}
You need to use Azure Storage for this. See the recipe in my book here: https://adrianhall.github.io/develop-mobile-apps-with-csharp-and-azure/chapter4/recipes/
There is no free option with Azure Storage.
Related
I'm a beginner in android xamarin programming and I'm trying to develop an app that needs to send a report mail using company cloud services (office365).
The main problem is that the user has no computer skills, therefore it is necessary to authenticate the app on azure (no login done by the user).
what is the correct identification flow? and what is the correct credential provider? ( on windows in a console app i used ClientSecretCredential, and it works fine!)
on android i tried :ClientSecretCredential, InteractiveBrowserCredential and UsernamePasswordCredential but i receive always the same error :
Message: An error occurred sending the request.
I don't understand if the request is not even sent or not answered or what?
here is my code:
async private void Button_buttonSendReport(object sender, EventArgs e)
{
AzureSettings settings = new AzureSettings();
settings.LoadSettings();
GraphHelper.InitializeGraphForAppOnlyAuth(settings);
try
{
await GraphHelper.SendMailAsync("Testing Microsoft Graph", "Hello!", "recipient#inwind.it");
Android.Widget.Toast.MakeText(this, "Mail sent.", Android.Widget.ToastLength.Long).Show();
}
catch (Exception ex)
{
Android.Widget.Toast.MakeText(this, "Error sending mail:"+ex.Message, Android.Widget.ToastLength.Long).Show();
}
}
public class AzureSettings
{
public string? ClientId { get; set; }
public string? ClientSecret { get; set; }
public string? TenantId { get; set; }
public string? AuthTenant { get; set; }
public string? redirectUri { get; set; }
public string[]? GraphUserScopes { get; set; }
public AzureSettings LoadSettings()
{
// Load settings
ClientId = "xxxxxxx-xxxx-xxxxxx-xxxx-xxxxxxxxx";
ClientSecret = "yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy";
TenantId = "zzzzzzzz-zzzzz-zzzzz-zzzzzzz-zzzzzzzzzzzz";
AuthTenant = "common";
GraphUserScopes = new[] { "User.Read.All", "User.Read", "Mail.Send" };
redirectUri = "msauth://com.companyname.packageName/<signature_of_app >";
return this;
}
}
this is initialization of client GraphHelper.InitializeGraphForAppOnlyAuth(settings);
public static void InitializeGraphForAppOnlyAuth(AzureSettings settings)
{
_settings = settings;
if (_appClient == null)
{
var options = new InteractiveBrowserCredentialOptions
{
TenantId = _settings.TenantId,
ClientId = _settings.ClientId,
AuthorityHost = AzureAuthorityHosts.AzurePublicCloud,
RedirectUri = new Uri(_settings.redirectUri),
};
// https://learn.microsoft.com/dotnet/api/azure.identity.interactivebrowsercredential
var interactiveCredential = new InteractiveBrowserCredential(options);
_appClient = new GraphServiceClient(interactiveCredential, _settings.GraphUserScopes);
}
}
this is GraphHelper.SendMailAsync
public static async Task SendMailAsync(string subject, string body, string recipient)
{
// Create a new message
var message = new Microsoft.Graph.Message
{
Subject = subject,
Body = new ItemBody
{
Content = body,ContentType = BodyType.Text
},
ToRecipients = new Recipient[]
{
new Recipient{EmailAddress = new EmailAddress {Address=recipient}}
}
};
await _appClient.Users["kkkkkkkkk-kkkkkk-kkkk-kkkkk-kkkkkkkkk"].SendMail(message).Request().PostAsync();
}
I have a view with an image: <Image Source="{Binding MainUrl}"/>
Image binding value : My image source on loading from database
the value of MainUrl variable is stored in an sqlite table.public string LogoUrl { get; set; }
viewmodel :
private string _logoUrl;
public string LogoUrl
{
get { return _logoUrl; }
set
{
SetValue(ref _logoUrl, value);
OnPropertyChanged(nameof(LogoUrl));
}
}
the user has the option to change the image using filepicker :
var file = await FilePicker.PickAsync(options);
if (file == null)
{
return;
}
var stream = await file.OpenReadAsync();
MainUrl = ImageSource.FromStream(() => stream);
LblImportLogo = file.FullPath;
Parameter.LogoUrl = file.FullPath;
when I choose an image with filepicker, this one is well displayed and the the path is saved into sqlite table but when I restart the application, the image is no longer displayed despite the path being well informed (this one is external). the problem is in all platforms, UWP,android and IOS. can you help me find a solution.
Mainpage.xaml.cs :
public MainPage()
{
var parameterStore = new SQLiteParameterStore(DependencyService.Get<ISQLiteDb>());
var pageService = new PageService();
ViewModel = new MainPageViewModel(parameterStore, pageService);
InitializeComponent();
NavigationPage.SetHasBackButton(this, false);
NavigationPage.SetHasNavigationBar(this, false);
}
protected override void OnAppearing()
{
ViewModel.LoadDataCommand.Execute(null);
base.OnAppearing();
}
public MainPageViewModel ViewModel
{
get { return BindingContext as MainPageViewModel; }
set { BindingContext = value; }
}
MainPageViewModel :
......
private ImageSource _mainurl;
public ImageSource MainUrl
{
get { return _mainurl; }
set
{
SetValue(ref _mainurl, value);
OnPropertyChanged(nameof(MainUrl));
}
}
......
private async Task LoadData()
{
var parameters = await _parameterStore.GetAll();
var elts = parameters.Count();
if (elts < 1)
{
//set default values
mainParameter = new Parameter();
mainParameter.LogoUrl = "default.jpg";
MainUrl = mainParameter.LogoUrl;
}
else
{
//set parameters
mainParameter = parameters[0];
MainUrl = ImageSource.FromFile(mainParameter.LogoUrl);
}
}
I am trying to get my Xamarin Forms App to read the appsettings.json config file for setting up RestSharp. I am targeting Android. The appsettings.json file is set to Copy Always in the root of my Android forms project in Visual Studio. I am trying to read in a Factory pattern.
internal class ConfigFactory
{
public ConfigFactory()
{
try
{
SetupSimpleConfiguration();
ReadSimpleConfiguration();
}
catch (Exception e)
{
Log.Logger.Error(e, "Exception Constructing Config Factory");
throw;
}
}
public RestClient QueryClient { get; private set; }
public RestClient CommandClient { get; private set; }
private IConfigurationRoot Config { get; set; }
private void ReadSimpleConfiguration()
{
string val1 = Config["QueryURL"];
string val2 = Config["CommandURL"];
QueryClient = new RestClient(val1);
CommandClient = new RestClient(val2);
}
private void SetupSimpleConfiguration()
{
var currentDirectory = System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal);
Log.Logger.Debug("Enter Simple {#Directory}", currentDirectory);
Config = new ConfigurationBuilder()
.SetBasePath(currentDirectory)
.AddJsonFile("appsettings.json")
.Build();
Log.Logger.Debug("Leave Simple {#Config}", Config);
}
}
The Exception I keep running into is:
The configuration file 'appsettings.json' was not found and is not
optional. The physical path is
'/data/user/0/com.companyname.SerilogSample/files/appsettings.json'
No matter what I set currentDirectory to.
I want to save a string from a TextArea to the device and then reload it after reopening the app. I have tried following the examples (link) but cant get it to work. Main problem arises when i try to read the file and use a StringInputConverter.
private void saveAndLoad(TextArea textArea){
File textFile = new File(ROOT_DIR,"text_file");
String text2 = textArea.getText();
String loadedFile = "none";
if (textFile.exists()){
FileClient fileClient = FileClient.create(textFile);
loadedFile = DataProvider.retrieveObject(fileClient.createObjectDataReader(
new StringInputConverter()));
}
try(FileWriter writer = new FileWriter(textFile)){
writer.write(textArea.getText());
} catch (IOException e) {
e.printStackTrace();
}
textArea.setText(text2);
}
Edit: inserted code which i tried to start reading file with and image of the error i am getting
If you check the DataProvider::retrieveObject documentation:
Retrieves an object using the specified ObjectDataReader. A GluonObservableObject is returned, that will contain the object when the read operation completed successfully.
It returns GluonObservableObject<String>, which is an observable wrapper of the string, not the string itself.
You need to get first the observable, and when the operation ends successfully you can retrieve the string:
if (textFile.exists()) {
FileClient fileClient = FileClient.create(textFile);
GluonObservableObject<String> retrieveObject = DataProvider
.retrieveObject(fileClient.createObjectDataReader(new StringInputConverter()));
retrieveObject.stateProperty().addListener((obs, ov, nv) -> {
if (ConnectState.SUCCEEDED.equals(nv)) {
loadedFile = retrieveObject.get();
}
});
}
This is a quick implementation of this functionality:
public class BasicView extends View {
private static final File ROOT_DIR;
static {
ROOT_DIR = Services.get(StorageService.class)
.flatMap(StorageService::getPrivateStorage)
.orElseThrow(() -> new RuntimeException("Error"));
}
private final File textFile;
private final TextField textField;
private String loadedFile = "none";
public BasicView(String name) {
super(name);
textFile = new File(ROOT_DIR, "text_file");
textField = new TextField();
VBox controls = new VBox(15.0, textField);
controls.setAlignment(Pos.CENTER);
controls.setPadding(new Insets(30));
setCenter(controls);
}
#Override
protected void updateAppBar(AppBar appBar) {
appBar.setNavIcon(MaterialDesignIcon.MENU.button(e -> System.out.println("Menu")));
appBar.setTitleText("Basic View");
appBar.getActionItems().add(MaterialDesignIcon.SAVE.button(e -> save()));
appBar.getActionItems().add(MaterialDesignIcon.RESTORE_PAGE.button(e -> restore()));
}
private void save() {
try (FileWriter writer = new FileWriter(textFile)) {
writer.write(textField.getText());
} catch (IOException ex) {
ex.printStackTrace();
}
}
private void restore() {
if (textFile.exists()) {
FileClient fileClient = FileClient.create(textFile);
GluonObservableObject<String> retrieveObject = DataProvider
.retrieveObject(fileClient.createObjectDataReader(new StringInputConverter()));
retrieveObject.stateProperty().addListener((obs, ov, nv) -> {
if (ConnectState.SUCCEEDED.equals(nv)) {
loadedFile = retrieveObject.get();
textField.setText(loadedFile);
}
});
}
}
}
i am new to Xamarin. I need to pass List of data as from one activity to another activity via intent .and get those data in another activity screen and process those data's. Please suggest a solution.
Thanks in advance
Use IList of generic type to pass data to the required data from one activity to another.
IList<String> Mon_year = new List<String>();
then pass this List in the intent
i.PutStringArrayListExtra("month_year",Mon_year);
In Another activity(where you want to get the sent data)
IList<String> Mon_year = Intent.GetStringArrayListExtra("month_year") ;// here u get the Ilist of String data...
I approached this a bit differently. I created a base class which inherited from Java.Lang.Object, ISerializable so that I could use Bundle.PutSerializable(string key, ISerializable value) to pass my objects between Activities.
[Flags]
public enum ObjectState
{
Normal = 0,
New = 1,
Modified = 2,
Removed = 4
}
public abstract class ObjectBase : Java.Lang.Object, ISerializable
{
public Guid Id { get; set; }
public ObjectState State { get; set; }
protected ObjectBase(IntPtr handle, JniHandleOwnership transfer) : base(handle, transfer)
{
}
protected ObjectBase()
{
}
[Export("readObject", Throws = new[] { typeof(IOException), typeof(Java.Lang.ClassNotFoundException) })]
private void ReadObject(ObjectInputStream stream)
{
this.Deserialize(stream);
}
[Export("writeObject", Throws = new[] { typeof(IOException), typeof(Java.Lang.ClassNotFoundException) })]
private void WriteObject(ObjectOutputStream stream)
{
this.Serialize(stream);
}
protected virtual void Deserialize(ObjectInputStream stream)
{
this.Id = Guid.Parse(stream.ReadUTF());
this.State = (ObjectState)stream.ReadInt();
}
protected virtual void Serialize(ObjectOutputStream stream)
{
stream.WriteUTF(this.Id.ToString());
stream.WriteInt((int)this.State);
}
}
public class Person : ObjectBase
{
public string Name { get; set; }
public int Age { get; set; }
public bool IsMarried { get; set; }
public DateTime? Anniversary { get; set; }
protected override void Deserialize(ObjectInputStream stream)
{
base.Deserialize(stream);
if (stream.ReadBoolean())
this.Name = stream.ReadUTF();
this.Age = stream.ReadInt();
this.IsMarried = stream.ReadBoolean();
if (stream.ReadBoolean())
this.Anniversary = DateTime.Parse(stream.ReadUTF());
}
protected override void Serialize(ObjectOutputStream stream)
{
base.Serialize(stream);
stream.WriteBoolean(this.Name != null);
if (this.Name != null)
stream.WriteUTF(this.Name);
stream.WriteInt(this.Age);
stream.WriteBoolean(this.IsMarried);
stream.WriteBoolean(this.Anniversary != null);
if (this.Anniversary != null)
stream.WriteUTF(this.Anniversary.Value.ToString(CultureInfo.InvariantCulture));
}
}
I can then pass a list of Person objects to a new Activity by doing:
List<Person> persons = new List<Person>();
var intent = new Intent(this, typeof(MyActivity));
var bundle = new Bundle();
for (int i = 0; i < persons.Count; i++)
{
var p = persons[i];
bundle.PutSerializable("Person" + i, p);
}
intent.PutExtras(bundle);
this.StartActivity(intent);
Then Recieve the objects like so:
protected override void OnCreate(Bundle bundle)
{
List<Person> persons = new List<Person>();
int i = 0;
while (bundle.ContainsKey("Person" + i))
{
var p = (Person) bundle.GetSerializable("Person" + i);
persons.Add(p);
i++;
}
base.OnCreate(bundle);
}
For passing a list of content from one activity to other activity you can use parcelable class. Its a type of bundle in which we can pass any type of content from one activity to other activity. The only thing is you just need to customize the parcelable class according to your need.
Please visit this Link or download this sample project so that you can understand more about passing list of content from one activity to other activity.
Hope this will solve your problem.