ServiceStack - dictionary to csv - c#

In my project I use ServiceStack to allow user to export data in csv format. It's ServiceStack that makes the job but it always sorts my dictionary by alphabetical and I don't want that. I want that my csv file have his columns in exact order that I inserted my data in the dictionary.
There is a way to configure ServiceStack to don't make this sort ?
My dictionary :
var excelResult = new Dictionary<string, string>();
excelResult["Id"] = x.Id.ToString();
excelResult["IBS Account Id"] = x.IBSAccountId.ToString(CultureInfo.InvariantCulture);
excelResult["IBS Order Id"] = x.IBSOrderId.ToString();
And in my csv file, instead of "Id, IBS Account Id,IBS Order Id" I have "IBS Account Id,IBS Order Id,Id"
Thank you very much !!

Here is how to unregister the default 'text/csv' content-type and serializer.
In AppHost.cs,
using ServiceStack.Common;
In AppHost Configure()...
SetConfig(new EndpointHostConfig
{
EnableFeatures = Feature.All.Remove(Feature.Csv),
});
Your apphost will no longer do any special handling for text/csv. At that point, you should be able to register it again as you wish, as explained in How to register your own custom format with ServiceStack.
Substitute your TapasSerializer for CsvSerializer and you should be good.
I discovered a clue how to do it from this SO answer.

Finally, I discover it's impossible(?) to override or custom ServiceStack CsvSerializer.
So, my solution (because I have to do something after all) : Create a new custom type, serialize it and write it in a csv file :)
Step 1 : (AppHOst.cs) Register my new type "tapas" (because I like tapas but I don't think my lead will be ok with that :p)
ContentTypeFilters.Register("text/x-tapas",
TapasSerializer.SerializeToStream, TapasSerializer.DeserializeFromStream);
Step 2 : (AppHOst.cs) Configure the response for the creation of the file
ResponseFilters.Add((req, res, dto) =>
{
if (req.ResponseContentType == "text/x-tapas")
{
res.AddHeader(HttpHeaders.ContentDisposition,
string.Format("attachment;filename={0}.csv", req.OperationName));
}
});
Step 3 : (myPage.handlebars) Swap csv with my new type in my request link (in java for my project)
<li>{{localize "ExportOrders"}}</li>
Step 4 : Your Tapas Serializer !
public class TapasSerializer
{
static TapasSerializer()
{
}
public static void SerializeToStream(IRequestContext requestContext, object response, Stream stream)
{
//TO DO
}
public static object DeserializeFromStream(Type type, Stream stream)
{
//TO DO
}
}
Thank you for reading !

Related

Adding object to JSON file

I'm creating a software on which I added a profiles feature where the user can create profile to load his informations faster. To store these informations, I'm using a JSON file, which contains as much objects as there are profiles.
Here is the format of the JSON file when a profile is contained (not the actual one, an example) :
{
"Profile-name": {
"form_email": "example#example.com",
//Many other informations...
}
}
Here is the code I'm using to write the JSON and its content :
string json = File.ReadAllText("profiles.json");
dynamic profiles = JsonConvert.DeserializeObject(json);
if (profiles == null)
{
File.WriteAllText(jsonFilePath, "{}");
json = File.ReadAllText(jsonFilePath);
profiles = JsonConvert.DeserializeObject<Dictionary<string, Profile_Name>>(json);
}
profiles.Add(profile_name.Text, new Profile_Name { form_email = form_email.Text });
var newJson = JsonConvert.SerializeObject(profiles, Formatting.Indented);
File.WriteAllText(jsonFilePath, newJson);
profile_tr.Nodes.Add(profile_name.Text, profile_name.Text);
debug_tb.Text += newJson;
But when the profiles.json file is completely empty, the profile is successfully written, but when I'm trying to ADD a profile when another one already exists, I get this error :
The best overloaded method match for 'Newtonsoft.Json.Linq.JObject.Add(string, Newtonsoft.Json.Linq.JToken)' has some invalid arguments on the profiles.Add(); line.
By the way, you can notice that I need to add {} by a non-trivial way in the file if it's empty, maybe it has something to do with the error ?
The expected output would be this JSON file :
{
"Profile-name": {
"form_email": "example#example.com",
//Many other informations...
},
"Second-profile": {
"form_email": "anotherexample#example.com"
//Some other informations...
}
}
Okay so I found by reading my code again, so I just replaced dynamic profiles = JsonConvert.DeserializeObject(json); to dynamic profiles = JsonConvert.DeserializeObject<Dictionary<string, Profile_Name>>(json);.
But it still don't fix the non-trivial way I use to add the {} to my file...
The object the first DeserializeObject method returns is actually a JObject, but below you deserialize it as a Dictionary. You shouldn't be mixing the types, choose either one.
If you use the JObject then to add objects you need to convert them to JObjects:
profiles.Add(profile_name.Text, JObject.FromObject(new Profile_Name { form_email = form_email.Text }));
In both cases, when the profile is null you just need to initialize it:
if (profiles == null)
{
profiles = new JObject(); // or new Dictionary<string, Profile_Name>();
}

Mongo.net - BypassDocumentValidation is not working

I'm trying to insert a json like this (fieldname with a "."), in a Net Core Console Project
{"name.field" : "MongoDB", "type" : "Database"}
Using the C# code belove:
-with InsertManyOptions with BypassDocumentValidation in true
var options = new InsertManyOptions
{
BypassDocumentValidation = true,
IsOrdered = false
};
await _collection.InsertManyAsync(items, options);
But I have this exception:
Element name 'name.field' is not valid
I´m using :
C# Mongo Driver 2.5
Net Core Project
MongoDB version 4.0.3
Any idea? Thanks!
The BypassDocumentValidation can be used to bypass the JSON Schema validation. The issue you are facing, however, is due to the C# driver which explicitly prevents the use of the dot symbol . as part of a field name.
This used to be required up until MongoDB v3.6 which officially added support for fields with ".".
Looking into the internals of the C# driver you can see that the BsonWriter.WriteName method calls contains this code which throws the Exception you're seeing:
if (!_elementNameValidator.IsValidElementName(name))
{
var message = string.Format("Element name '{0}' is not valid'.", name);
throw new BsonSerializationException(message);
}
The _elementNameValidator is something that is managed internally by the driver which in fact comes with a NoOpElementNameValidator that doesn't do any validations. The driver, however, won't use this validator for "normal" collections.
All that said, I would strongly advise against the use of field names with "unusual" characters anyway because this is likely to set you up for unexpected behaviour and all sorts of other issues down the road.
In order to get around this you can do one of the following things:
a) Write your own custom serializer which is an option that I would personally steer clear off if possible - it adds complexity that most of the time shouldn't be required.
b) Use the below helper extension (copied from one of the unit testing projects inside the driver) to convert the BsonDocument into a RawBsonDocument which can then successfully written to the server:
public static class RawBsonDocumentHelper
{
public static RawBsonDocument FromBsonDocument(BsonDocument document)
{
using (var memoryStream = new MemoryStream())
{
using (var bsonWriter = new BsonBinaryWriter(memoryStream, BsonBinaryWriterSettings.Defaults))
{
var context = BsonSerializationContext.CreateRoot(bsonWriter);
BsonDocumentSerializer.Instance.Serialize(context, document);
}
return new RawBsonDocument(memoryStream.ToArray());
}
}
public static RawBsonDocument FromJson(string json)
{
return FromBsonDocument(BsonDocument.Parse(json));
}
}
And then simply write the RawBsonDocument to the server:
RawBsonDocument rawDoc = RawBsonDocumentHelper.FromJson("{\"name.field\" : \"MongoDB\", \"type\" : \"Database\"}");
collection.InsertOne(rawDoc);

Using Json to Create FormDialog

I am trying to build user and conversation specific dialogs using json schema and I have the LINQ queries generating the json perfectly. If I save a sample of the json to disk and use it like the annotatedsandwich example where it is read from a file on disk, it works great. The json is unique per user and conversation and instead of writing to disk I want to use it in memory. I do not see how to pass the json string to the BuildJsonForm method or alternately how to get the userID information in the BuildJsonForm method in order to generate the json based on the user and conversation. I know I am missing something that will let me do this but I am not finding it. Any assistance with how this should be done would be appreciated. Thank you.
Instead of doing (using the AnnotatedSandwich code)
FormDialog.FromForm(SandwichOrder.BuildJsonForm)
You could just build the BuildFormDelegate and pass your parameters:
string schema = "your jsonform schema";
BuildFormDelegate<JObject> formDelegate = () => SandwichOrder.BuildJsonForm(schema);
FormDialog.FromForm(formDelegate)
Create a custom form builder to which you pass your custom form json schema
[Serializable]
public class CustomFormBuilder
{
public string FormJson { get; set; }
public CustomFormBuilder(string formJson)
{
FormJson = formJson;
}
public IForm<JObject> BuildJsonForm()
{
var schema = JObject.Parse(FormJson);
var form = new FormBuilderJson(schema)
.AddRemainingFields()
.Build();
return form;
}
}
Use as follows (where formJson is your user specific form)
var formBuilder = new CustomFormBuilder(formJson);
var jsonFormDialog = FormDialog.FromForm(
formBuilder.BuildJsonForm,
FormOptions.PromptInStart);
This will avoid the ClosureCaptureExcept‌​ion.

Keeping track of user customization's c#

Good evening; I have an application that has a drop down list; This drop down list is meant to be a list of commonly visited websites which can be altered by the user.
My question is how can I store these values in such a manor that would allow the users to change it.
Example; I as the user, decide i want google to be my first website, and youtube to be my second.
I have considered making a "settings" file however is it practical to put 20+ websites into a settings file and then load them at startup? Or a local database, but this may be overkill for the simple need.
Please point me in the right direction.
Given you have already excluded database (probably for right reasons.. as it may be over kill for a small app), I'd recommend writing the data to a local file.. but not plain text..
But preferably serialized either as XML or JSON.
This approach has at least two benefits -
More complex data can be stored in future.. example - while order can be implicit, it can be made explicit.. or additional data like last time the url was used etc..
Structured data is easier to validate against random corruption.. If it was a plain text file.. It will be much harder to ensure its integrity.
The best would be to use the power of Serializer and Deserializer in c#, which will let you work with the file in an Object Oriented. At the same time you don't need to worry about storing into files etc... etc...
Here is the sample code I quickly wrote for you.
using System;
using System.IO;
using System.Collections;
using System.Xml.Serialization;
namespace ConsoleApplication3
{
public class UrlSerializer
{
private static void Write(string filename)
{
URLCollection urls = new URLCollection();
urls.Add(new Url { Address = "http://www.google.com", Order = 1 });
urls.Add(new Url { Address = "http://www.yahoo.com", Order = 2 });
XmlSerializer x = new XmlSerializer(typeof(URLCollection));
TextWriter writer = new StreamWriter(filename);
x.Serialize(writer, urls);
}
private static URLCollection Read(string filename)
{
var x = new XmlSerializer(typeof(URLCollection));
TextReader reader = new StreamReader(filename);
var urls = (URLCollection)x.Deserialize(reader);
return urls;
}
}
public class URLCollection : ICollection
{
public string CollectionName;
private ArrayList _urls = new ArrayList();
public Url this[int index]
{
get { return (Url)_urls[index]; }
}
public void CopyTo(Array a, int index)
{
_urls.CopyTo(a, index);
}
public int Count
{
get { return _urls.Count; }
}
public object SyncRoot
{
get { return this; }
}
public bool IsSynchronized
{
get { return false; }
}
public IEnumerator GetEnumerator()
{
return _urls.GetEnumerator();
}
public void Add(Url url)
{
if (url == null) throw new ArgumentNullException("url");
_urls.Add(url);
}
}
}
You clearly need some sort of persistence, for which there are a few options:
Local database
- As you have noted, total overkill. You are just storing a list, not relational data
Simple text file
- Pretty easy, but maybe not the most "professional" way. Using XML serialization to this file would allow for complex data types.
Settings file
- Are these preferences really settings? If they are, then this makes sense.
The Registry - This is great for settings you don't want your users to ever manually mess with. Probably not the best option for a significant amount of data though
I would go with number 2. It doesn't sound like you need any fancy encoding or security, so just store everything in a text file. *.ini files tend to meet this description, but you can use any extension you want. A settings file doesn't seem like the right place for this scenario.

Why is System.Data.Services.MimeTypeAttribute now only a class level attribute?

I'm getting started with Astoria/ADO.NET Data Services/WCF Data Services. Looking through a lot of the code samples out there, it appears that the MimeType attribute used to be a method level attribute. After installing the latest update, it is now a class level attribute.
If I have more than one Service Operation that I want to return as a certain MimeType, then it appears now that I have to create a new service for each operation. Is this correct?
Most examples are like this:
[WebGet]
[SingleResult]
[MimeType("application/pdf")]
public IQueryable<byte[]> FooPDF()
{
var result = from p in this.CurrentDataSource.MyPDFs
where p.FooID == 2
select p;
return result.Take(1).Select(p => p.PDF);
}
I get "Attribute 'MimeType' is not valid on this declaration type. It is only valid on 'class' declarations." when I compile, because now I can't do this.
Now, I have to do this:
[MimeType("FooPDF", "application/pdf")]
public class FooService : DataService<FooDBEntities>
{
public static void InitializeService(DataServiceConfiguration config)
{
config.SetServiceOperationAccessRule("FooPDF", ServiceOperationRights.All);
}
[WebGet]
[SingleResult]
public IQueryable<byte[]> FooPDF()
{
var result = from p in this.CurrentDataSource.MyPDFs
where p.FooID == 2
select p;
return result.Take(1).Select(p => p.PDF);
}
}
What's worse is that I can't add duplicate MimeType attributes to my class.
Is all of this really by design, or am I missing something?
Thanks for reporting this bug to us. I have opened this at our end to track this issue
With the recent update, we added the support for blobs as a first class concept in the data services. If you have a blob association with an entity, then both server and client have ways to surface this. To know more about this, please refer to the following link: http://msdn.microsoft.com/en-us/library/ee473426(v=VS.100).aspx
Hope this helps.
Thanks
Pratik
[MSFT]

Categories

Resources