Saving Custom List in NSUserDefaults with Monotouch - xamarin.ios

I have been playing with this for a few hours and I'm stuck. I'm trying to save a list of Favorite objects in the NSUserDefaults using Monotouch. I believe that I am on the right track but I just can't quite get it... here are my model objects:
public class Favorite {
public Favorite (){}
public string Description {get;set;}
public Song Song {get;set;}
}
public class Song {
public Song (){}
public string Name {get;set;}
public string Artist {get;set;}
}
Next, I want to save a list of Favorites that the user has selected. From what I have read, I can use an NSArray to save a list of items in the NSUserDefaults. So how do I go from a List of Favorites to an NSArray of Favorites... I haven't been able to find any documentation on this. Here is my Settings wrapper:
public class Settings {
private static string _favoritesKey = "favorites";
public static IList<Favorite> Favorites {get;set;}
public static void Add(Favorite favorite){
Favorites.Add(favorite);
}
public static void Remove(Favorite favorite){
Favorites.Remove(favorite);
}
public static void Read()
{
var tempFavorites = (NSArray)NSUserDefaults.StandardUserDefaults[_favoritesKey];
if(tempFavorites == null){
Favorites = new List<Favorite>();
}
else {
for(uint i=0;i<tempFavorites.Count;i++){
var fav = tempFavorites.ValueAt(i); //returns IntPtr
// do something to convert to Favorite
// Favorites.Add(converted_favorite);
}
}
}
public static void Write()
{
var tempArray = Favorites.ToArray();
// convert to NSObject[]
NSUserDefaults.StandardUserDefaults[_favoritesKey] = NSArray.FromNSObjects(converted_array);
NSUserDefaults.StandardUserDefaults.Synchronize();
}
}
Am I on the right track? It looks like all I need to do is figure out how to convert to and from NSObjects. Also, if I am saving these custom objects in NSUserDefaults, do they need to be serializable? Much thanks!

If you want to do this, you would need your Favorite class to be a NSObject with native storage that you synchronize with the [Connect] attribute, something like this:
[Register]
public class Favorite : NSObject {
[Connect]
public string Description {
get {
return (string) this.GetNativeField ("Description");
}
set {
this.SetNativeField ("Description", new NSString (value));
}
}
}
You would do the same for your Song class. You can only store native classes in the NSStandardUserDefaults object store.
An alternative would be what Jason suggested and just serialize to a string and then store that as a NSString.

I would try serializing them and then convert to NSString.

Editing, found the way around it:
var Chosenkey = new object[] { NSUserDefaults.StandardUserDefaults.StringForKey("FooIdentifier").ToString() };
var DefValueToSet = new object[] { "Foo" };
var newInfo = NSDictionary.FromObjectsAndKeys(Chosenkey,DefValueToSet);
And then we register this NSDictionary to user defaults and we're done.
I am trying to serialize and then convert the result to a NSstring, but I am still getting the nullexception when trying to create the dictionary:
This is my declaration:
string llalalala = "Thisisatest";
NSString test = SerializeToString(llalalala);
NSDictionary dictionary = new NSDictionary(test, test,null);
This is my method to serialise:
//Method to serialize objects
public NSString SerializeToString(object obj)
{
XmlSerializer serializer = new XmlSerializer(obj.GetType());
using (StringWriter writer = new StringWriter())
{
serializer.Serialize(writer, obj);
try
{
return (NSString)writer.ToString();
}
catch (Exception ex)
{
return new NSString("");
}
}
}
am I doing anything wrong? the NSstrings are not empty so I have no clue what´s going on here..

Related

Automapper flattening embedded objects

I have a class (many more fields than defined below, but you get the basic idea):
public class Embedded
{
public int Field1{get;set;}
}
public class Source
{
public int Field2{get;set;}
public Embedded Embedded{get;set;}
}
public class Destination
{
public int Field1{get;set;}
public int Field2{get;set;}
}
The normal approach would be:
Mapper.Initialise(cfg=>
{
cfg.CreateMap<Source, Destination>(dest=>dest.Field1, opt=>opt.MapFrom(src=>src.Embedded.Field1));
}
My Embedded object has many fields (and I have multiple embedded objects) and they will map by convention to the fields in the Destination object.
I need something like the functionality provided by IncludeBase<> but to be able to specify which field should be used to use as the src.
Is there a simpler way of doing this?
I found Map<,>(s,d) and AfterMap:
Mapper.Initialize(cfg=>{
cfg.CreateMap<Embedded, Destination>();
cfg.CreateMap<Source, Destination>()
.AfterMap((s,d) {
Mapper.Map(s.Embedded, d);
}
});
var src = new Source{
Embedded = new Embedded();
}
var dest = Mapper.Map<Source, Destination>(src);

unable to deserialise odata to Dataset using JSON.NET due to odata.metadata

I'm trying to get some help on deserializing a JSON reponse to a DataSet.
in theory this should be easy as using this example
http://www.newtonsoft.com/json/help/html/DeserializeDataSet.htm
DataSet dataSet = JsonConvert.DeserializeObject<DataSet>(json);
DataTable dataTable = dataSet.Tables["Table1"];
However the JSON I am getting back is supplemented / decorated using "odata.metadata"
see below.
{"odata.metadata":"http://nodts004.cloudapp.net:7058/TNPMaster2016Dev/OData/$metadata#NP_Customer","value":[{"No":"01121212","Name":"Spotsmeyer's Furnishings","City":"Miami","Amount":"0","Customer_Posting_Group":"FOREIGN","Balance_LCY":"0","Sales_LCY":"0","Profit_LCY":"0","Balance_Due_LCY":"0","Payments_LCY":"0","Inv_Amounts_LCY":"0","Cr_Memo_Amounts_LCY":"0","Outstanding_Orders":"0","Shipped_Not_Invoiced":"0","No_of_Quotes":0,"No_of_Blanket_Orders":0,"No_of_Orders":6,"No_of_Invoices":0,"No_of_Return_Orders":0,"No_of_Credit_Memos":0,"No_of_Pstd_Shipments":0,"No_of_Pstd_Invoices":0,"No_of_Pstd_Return_Receipts":0,"No_of_Pstd_Credit_Memos":0,"No_of_Ship_to_Addresses":0,"Outstanding_Orders_LCY":"0","Shipped_Not_Invoiced_LCY":"0"},{"No":"01445544","Name":"Progressive Home Furnishings","City":"Chicago","Amount":"0","Customer_Posting_Group":"FOREIGN","Balance_LCY":"1499.02","Sales_LCY":"1499.02","Profit_LCY":"305.12","Balance_Due_LCY":"1499.02","Payments_LCY":"0","Inv_Amounts_LCY":"1499.02","Cr_Memo_Amounts_LCY":"0","Outstanding_Orders":"0","Shipped_Not_Invoiced":"0","No_of_Quotes":0,"No_of_Blanket_Orders":0,"No_of_Orders":0,"No_of_Invoices":0,"No_of_Return_Orders":0,"No_of_Credit_Memos":0,"No_of_Pstd_Shipments":1,"No_of_Pstd_Invoices":1,"No_of_Pstd_Return_Receipts":0,"No_of_Pstd_Credit_Memos":0,"No_of_Ship_to_Addresses":0,"Outstanding_Orders_LCY":"0","Shipped_Not_Invoiced_LCY":"0"},{"No":"01454545","Name":"New Concepts Furniture","City":"Atlanta","Amount":"0","Customer_Posting_Group":"FOREIGN","Balance_LCY":"222241.32","Sales_LCY":"0","Profit_LCY":"0","Balance_Due_LCY":"222241.32","Payments_LCY":"0","Inv_Amounts_LCY":"222241.32","Cr_Memo_Amounts_LCY":"0","Outstanding_Orders":"15609","Shipped_Not_Invoiced":"0","No_of_Quotes":0,"No_of_Blanket_Orders":0,"No_of_Orders":1,"No_of_Invoices":0,"No_of_Return_Orders":0,"No_of_Credit_Memos":0,"No_of_Pstd_Shipments":0,"No_of_Pstd_Invoices":0,"No_of_Pstd_Return_Receipts":0,"No_of_Pstd_Credit_Memos":0,"No_of_Ship_to_Addresses":0,"Outstanding_Orders_LCY":"8702.82","Shipped_Not_Invoiced_LCY":"0"},{"No":"01905893","Name":"Candoxy Canada Inc.","City":"Thunder Bay","Amount":"0","Customer_Posting_Group":"FOREIGN","Balance_LCY":"0","Sales_LCY":"0","Profit_LCY":"0","Balance_Due_LCY":"0","Payments_LCY":"0","Inv_Amounts_LCY":"0","Cr_Memo_Amounts_LCY":"0","Outstanding_Orders":"0","Shipped_Not_Invoiced":"0","No_of_Quotes":0,"No_of_Blanket_Orders":0,"No_of_Orders":0,"No_of_Invoices":0,"No_of_Return_Orders":0,"No_of_Credit_Memos":0,"No_of_Pstd_Shipments":0,"No_of_Pstd_Invoices":0,"No_of_Pstd_Return_Receipts":0,"No_of_Pstd_Credit_Memos":0,"No_of_Ship_to_Addresses":0,"Outstanding_Orders_LCY":"0","Shipped_Not_Invoiced_LCY":"0"},{"No":"01905899","Name":"Elkhorn Airport","City":"Elkhorn","Amount":"0","Customer_Posting_Group":"FOREIGN","Balance_LCY":"0","Sales_LCY":"0","Profit_LCY":"0","Balance_Due_LCY":"0","Payments_LCY":"0","Inv_Amounts_LCY":"0","Cr_Memo_Amounts_LCY":"0","Outstanding_Orders":"0","Shipped_Not_Invoiced":"0","No_of_Quotes":0,"No_of_Blanket_Orders":0,"No_of_Orders":0,"No_of_Invoices":0,"No_of_Return_Orders":0,"No_of_Credit_Memos":0,"No_of_Pstd_Shipments":0,"No_of_Pstd_Invoices":0,"No_of_Pstd_Return_Receipts":0,"No_of_Pstd_Credit_Memos":0,"No_of_Ship_to_Addresses":0,"Outstanding_Orders_LCY":"0","Shipped_Not_Invoiced_LCY":"0"}]}
I have for certain scenarios created a POCO to deal with the returned json for the properties
public class RootObject2
{
[JsonProperty("odata.metadata")]
public string odatametadata { get; set; }
[JsonProperty("odata.nextLink")]
public string NextLinkUrl { get; set; }
}
and
public class RootObject
{
[JsonProperty("odata.metadata")]
public string odatametadata { get; set; }
[JsonProperty("odata.nextLink")]
public string NextLinkUrl { get; set; }
public List<UrlItem> Value { get; set; }
}
These are used in instances where I know the returned JSON will contain certain structures and can be safely dealt with.
The problem is that the VALUE part of the JSON will be dynamic in many instances and I wanted to take advantage of the dynamic nature of the JSONConvert functions to build DataSets that I can then pass through as a source for an Excel table. It should be noted that the data coming back will never be definable.
When i use the code:
DataSet dataSet = JsonConvert.DeserializeObject<DataSet>(json);
I get an error, because I need to be passing the sting / contents of the VALUE node/element to the DeserialseObject.
Is there a setting on the JSON converter that allows this?
I have tried to create a POCO with a string field and then after mapping the VALUE node to the POCO passing the string to the JSONConverter but this errors out.
A solution to this would be most helpful.
Thanks.
B....
You can create your own custom subclass of DataSetConverter that strips out non-array-valued properties from the root DataSet object:
public class DataSetConverter : Newtonsoft.Json.Converters.DataSetConverter
{
public override bool CanConvert(Type valueType)
{
if (!base.CanConvert(valueType))
return false;
return typeof(DataSet).IsAssignableFrom(valueType);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
{
return null;
}
var token = JObject.Load(reader);
// Strip non-array-valued properties
foreach (var property in token.Properties().Where(p => p.Value.Type != JTokenType.Array).ToList())
property.Remove();
using (var subReader = token.CreateReader())
{
while (subReader.TokenType == JsonToken.None)
subReader.Read();
return base.ReadJson(subReader, objectType, existingValue, serializer); // Use base class to convert
}
}
}
Then use it as follows:
var dataSet = JsonConvert.DeserializeObject<DataSet>(json, new JsonSerializerSettings { Converters = new JsonConverter[] { new DataSetConverter() } });
var dataTable = dataSet.Tables["value"];
Prototype fiddle.

How to retrieve data using a strong typed model in LinqToSql

This code works fine.
using (ContextDB db = new ContextDB())
{
var custAcct = (from c in db.CustAccts
select new
{
c.AcctNo,
c.Company,
c.UserName
}).ToList();
But this one doesn't
public class CustAcct
{
public int AcctNo { get; set; }
public string Company { get; set; }
public string UserName { get; set; }
}
....
....
....
using (ContextDB db = new ContextDB())
{
CustAcct custAcct = (from c in db.CustAccts
select new
{
c.AcctNo,
c.Company,
c.UserName
}).ToList();
It returns this error:
Cannot implicitly convert type 'System.Collections.Generic.IEnumerable' to 'EMailReader.Models.CustAcct'. An explicit conversion exists (are you missing a cast?)
I used Google, found many related topics but still couldn't put it to work using the available solutions
I just need to return data to a strong typed model.
EDITED:
After more research I found this solution bellow, but I wonder why I cannot retrieve directly in the list from LinqToSql.
List<CustAcct> temp = new List<CustAcct>();
IEnumerable<dynamic> items = custAcct;
foreach (var item in items)
{
temp.Add(new CustAcct()
{
AcctNo = item.AcctNo,
Company = item.Company,
UserName = item.UserName,
});
}
You are re defining those properties by creating new Class. And this will override LINQ2SQL generated class.
Just change "public class CustAcct" to "public partial class CustAcct".
This will solve your problem, and you do not need to define those properties again. Remove those from your class. Those will be automatically create for you.
If you can just post your class, and I will change it for you.
//Shyam

How do I create Enumerable<Func<>> out of method instances

I am creating a rule set engine that looks kinda like a unit test framework.
[RuleSet(ContextA)]
public class RuleSet1
{
[Rule(TargetingA)]
public Conclusion Rule1(SubjectA subject)
{ Create conclusion }
[Rule(TargetingA)]
public Conclusion Rule2(SubjectA subject)
{ Create conclusion }
[Rule(TargetingB)]
public Conclusion Rule3(SubjectB subject)
{ Create conclusion }
}
[RuleSet(ContextB)]
public class RuleSet2
{
[Rule(TargetingB)]
public Conclusion Rule1(SubjectB subject)
{ Create conclusion }
[Rule(TargetingA)]
public Conclusion Rule2(SubjectA subject)
{ Create conclusion }
[Rule(TargetingB)]
public Conclusion Rule3(SubjectB subject)
{ Create conclusion }
}
public class Conclusion()
{
// Errorcode, Description and such
}
// contexts and targeting info are enums.
The goal is to create an extensible ruleset that doesn't alter the API from consumer POV while having good separation-of-concerns within the code files. Again: like a unit test framework.
I am trying to create a library of these that expose the following API
public static class RuleEngine
{
public static IEnumerable<IRuleSet> RuleSets(contextFlags contexts)
{
{
return from type in Assembly.GetExecutingAssembly().GetTypes()
let attribute =
type.GetCustomAttributes(typeof (RuleSetAttribute), true)
.OfType<RuleSetAttribute>()
.FirstOrDefault()
where attribute != null
select ?? I don't know how to convert the individual methods to Func's.
}
}
}
internal interface IRuleset
{
IEnumerable<Func<SubjectA, Conclusion>> SubjectARules { get; }
IEnumerable<Func<SubjectB, Conclusion>> SubjectBRules { get; }
}
...which allows consumers to simply use like this (using foreach instead of LINQ for readability in this example)
foreach (var ruleset in RuleEgine.RuleSets(context))
{
foreach (var rule in ruleset.SubjectARules)
{
var conclusion = rule(myContextA);
//handle the conclusion
}
}
Also, it would be very helpful if you could tell me how to get rid of "TargetingA" and "TargetingB" as RuleAttribute parameters and instead use reflection to inspect the parameter type of the decorated method directly. All the while maintaining the same simple external API.
You can use Delegate.CreateDelegate and the GetParameters method to do what you want.
public class RuleSet : IRuleSet
{
public IEnumerable<Func<SubjectA, Conclusion>> SubjectARules { get; set; }
public IEnumerable<Func<SubjectB, Conclusion>> SubjectBRules { get; set; }
}
public static class RuleEngine
{
public static IEnumerable<IRuleSet> RuleSets() // removed contexts parameter for brevity
{
var result = from t in Assembly.GetExecutingAssembly().GetTypes()
where t.GetCustomAttributes(typeof(RuleSetAttribute), true).Any()
let m = t.GetMethods().Where(m => m.GetCustomAttributes(typeof(RuleAttribute)).Any()).ToArray()
select new RuleSet
{
SubjectARules = CreateFuncs<SubjectA>(m).ToList(),
SubjectBRules = CreateFuncs<SubjectB>(m).ToList()
};
return result;
}
}
// no error checking for brevity
// TODO: use better variable names
public static IEnumerable<Func<T, Conclusion>> CreateFuncs<T>(MethodInfo[] m)
{
return from x in m
where x.GetParameters()[0].ParameterType == typeof(T)
select (Func<T, Conclusion>)Delegate.CreateDelegate(typeof(Func<T, Conclusion>), null, x);
}
Then you can use it like this:
var sa = new SubjectA();
foreach (var ruleset in RuleEngine.RuleSets())
{
foreach (var rule in ruleset.SubjectARules)
{
var conclusion = rule(sa);
// do something with conclusion
}
}
In your LINQ query you headed straight for RuleSetAttribute, and so lost other information. If you break the query in several lines of code you can get methods from the type with GetMethods(), and then you can call GetCustomAttribute<RuleAttribute>().

Get job title using System.DirectoryServices.AccountManagement

I've successfully used the AccountManagement code to retrieve basic AD information but it's only returning a very limited set of information about the returned object. How can I get extended information from AD using the AccountManagement functionality. Specifically the Job Title or title as it seems to be called in my instance of AD.
I know how to do it using the older DirectoryServices but I'd like to know how to do it using the new namespace.
Yes, the default set of properties on UserPrincipal is quite limited - but the great part is: there's a neat extensibility story in place!
You need to define a class descending from UserPrincipal and then you can very easily get access to a lot more properties, if needed.
The skeleton would look something like this:
namespace ADExtended
{
[DirectoryRdnPrefix("CN")]
[DirectoryObjectClass("User")]
public class UserPrincipalEx : UserPrincipal
{
// Inplement the constructor using the base class constructor.
public UserPrincipalEx(PrincipalContext context) : base(context)
{ }
// Implement the constructor with initialization parameters.
public UserPrincipalEx(PrincipalContext context,
string samAccountName,
string password,
bool enabled) : base(context, samAccountName, password, enabled)
{}
UserPrincipalExSearchFilter searchFilter;
new public UserPrincipalExSearchFilter AdvancedSearchFilter
{
get
{
if (null == searchFilter)
searchFilter = new UserPrincipalExSearchFilter(this);
return searchFilter;
}
}
// Create the "Title" property.
[DirectoryProperty("title")]
public string Title
{
get
{
if (ExtensionGet("title").Length != 1)
return string.Empty;
return (string)ExtensionGet("title")[0];
}
set { ExtensionSet("title", value); }
}
// Implement the overloaded search method FindByIdentity.
public static new UserPrincipalEx FindByIdentity(PrincipalContext context, string identityValue)
{
return (UserPrincipalEx)FindByIdentityWithType(context, typeof(UserPrincipalEx), identityValue);
}
// Implement the overloaded search method FindByIdentity.
public static new UserPrincipalEx FindByIdentity(PrincipalContext context, IdentityType identityType, string identityValue)
{
return (UserPrincipalEx)FindByIdentityWithType(context, typeof(UserPrincipalEx), identityType, identityValue);
}
}
}
And that's really almost all there is! The ExtensionGet and ExtensionSet methods allow you to "reach down" into the underlying directory entry and grab out all the attributes you might be interested in....
Now, in your code, use your new UserPrincipalEx class instead of UserPrincipal:
using (PrincipalContext ctx = new PrincipalContext(ContextType.Domain))
{
// Search the directory for the new object.
UserPrincipalEx myUser = UserPrincipalEx.FindByIdentity(ctx, "someUserName");
if(myUser != null)
{
// get the title which is now available on your "myUser" object!
string title = myUser.Title;
}
}
Read all about the System.DirectoryServices.AccountManagement namespace and its extensibility story here:
Managing Directory Security Principals in the .NET Framework 3.5
Update: sorry - here's the UserPrincipalExSearchFilter class - missed that one in the original post. It just shows the ability to also extend the search filters, if need be:
public class UserPrincipalExSearchFilter : AdvancedFilters
{
public UserPrincipalExSearchFilter(Principal p) : base(p) { }
public void LogonCount(int value, MatchType mt)
{
this.AdvancedFilterSet("LogonCount", value, typeof(int), mt);
}
}
To Augment the above I have knocked up an extension method to call ExtensionGet. It uses reflection to get hold of the protected method you would otherwise have to inherit. You might need to use this if you are returning UserPrincipalObjects from Groups.Members, for example
public static class AccountManagmentExtensions
{
public static string ExtensionGet(this UserPrincipal up, string key)
{
string value = null;
MethodInfo mi = up.GetType()
.GetMethod("ExtensionGet", BindingFlags.NonPublic | BindingFlags.Instance);
Func<UserPrincipal, string, object[]> extensionGet = (k,v) =>
((object[])mi.Invoke(k, new object[] { v }));
if (extensionGet(up,key).Length > 0)
{
value = (string)extensionGet(up, key)[0];
}
return value;
}
}
There are simpler ways of getting to that info. Here is the way I got to Job Title in VB.NET:
Dim yourDomain As New PrincipalContext(ContextType.Domain, "yourcompany.local")
Dim user1 As UserPrincipal = UserPrincipal.FindByIdentity(yourDomain, principal.Identity.Name)
Dim Entry As DirectoryServices.DirectoryEntry = user1.GetUnderlyingObject()
Dim JobTitle As String = Entry.Properties.Item("Title").Value.ToString
To expand on Programmierus' comment, here is a simple way to do this on the fly in C#.
public static string GetProperty(UserPrincipal userPrincipal, string property)
{
DirectoryEntry d = (DirectoryEntry)userPrincipal.GetUnderlyingObject();
return d.Properties[property]?.Value?.ToString();
}

Resources