I have below api url
http://myapi/api/getproduct?
These parameters will get created from below class
public class ApiParamters
{
public string id {get;set;}
public string name {get;set;}
public List<string> otherNames {get;set;}
}
If value of above parameters are
id=1
name="product1"
OtherNames="oldProduct1" and "oldProduct2"
Then api url should be as below
http://myapi/api/getproduct?id=1&name=product1&OtherNames=oldProduct1&OtherNames=oldProduct2
How to dynamically create these kind of url (GENERIC SOLUTION IS REQUREID because I have to implement simliar logic for other APIs too)
Here's what I came up with using reflection. I'm sure it doesn't handle all the cases, but see if it works for you.
using System.Web;
using System.Reflection;
using System.Collections.Generic;
class Program {
public static string toQueryString(string url, object o)
{
var query = HttpUtility.ParseQueryString(string.Empty);
FieldInfo[] myField = o.GetType().GetFields();
for (int i = 0; i < myField.Length; i++)
{
// Is it a list?
var t = myField[i].GetValue(o) as System.Collections.IEnumerable;
if (t != null)
{
dynamic lst = myField[i].GetValue(o);
int index = 1;
foreach (dynamic oo in lst)
{
query[myField[i].Name + index++] = oo.ToString();
}
}
else
{
query[myField[i].Name] = myField[i].GetValue(o).ToString();
}
}
return query.Count == 0 ? url : url + "?" + query.ToString();
}
}
Related
I am trying to make a source generator for mapping columns from the google bigquery api client to class properties. I'm having trouble getting custom column names from a ColumnAttribute on the properties. ConstructorArguments is always empty and columnAttribute.AttributeClass in this sample is always an ErrorTypeSymbol. If I try to load that type using compilation.GetTypeByMetadataName("System.ComponentModel.DataAnnotations.Schema.ColumnAttribute") the result is always null.
using System.Collections.Immutable;
using System.Diagnostics;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
namespace BigQueryMapping;
[Generator]
public class BigQueryMapperGenerator : IIncrementalGenerator
{
public void Initialize(IncrementalGeneratorInitializationContext context)
{
// add marker attribute
context.RegisterPostInitializationOutput(ctx =>
ctx.AddSource("BigQueryMappedAttribute.g.cs", SourceText.From(Attribute, Encoding.UTF8)));
// add static interface
context.RegisterPostInitializationOutput(ctx =>
ctx.AddSource("BigQueryMappedInterface.g.cs", SourceText.From(Interface, Encoding.UTF8)));
// get classes
IncrementalValuesProvider<ClassDeclarationSyntax> classDeclarations = context.SyntaxProvider
.CreateSyntaxProvider(
predicate: static (s, _) => s is ClassDeclarationSyntax c && c.AttributeLists.Any(),
transform: static (ctx, _) => GetSemanticTargetForGeneration(ctx)
)
.Where(static m => m is not null)!;
IncrementalValueProvider<(Compilation Compilation, ImmutableArray<ClassDeclarationSyntax>Syntaxes)>
compilationAndClasses = context.CompilationProvider.Combine(classDeclarations.Collect());
context.RegisterSourceOutput(compilationAndClasses,
static (spc, source) => Execute(source.Compilation, source.Syntaxes, spc));
static ClassDeclarationSyntax? GetSemanticTargetForGeneration(GeneratorSyntaxContext context)
{
var classDeclarationSyntax = (ClassDeclarationSyntax)context.Node;
foreach (var attributeListSyntax in classDeclarationSyntax.AttributeLists)
{
foreach (var attributeSyntax in attributeListSyntax.Attributes)
{
var fullName = context.SemanticModel.GetTypeInfo(attributeSyntax).Type?.ToDisplayString();
if (fullName == "BigQueryMapping.BigQueryMappedAttribute")
return classDeclarationSyntax;
}
}
return null;
}
static void Execute(Compilation compilation, ImmutableArray<ClassDeclarationSyntax> classes,
SourceProductionContext context)
{
try
{
if (classes.IsDefaultOrEmpty)
return;
var distinctClasses = classes.Distinct();
var classesToGenerate = GetTypesToGenerate(compilation, distinctClasses, context.CancellationToken);
foreach (var classToGenerate in classesToGenerate)
{
var result = GeneratePartialClass(classToGenerate);
context.AddSource($"{classToGenerate.RowClass.Name}.g.cs", SourceText.From(result, Encoding.UTF8));
}
}
catch (Exception e)
{
var descriptor = new DiagnosticDescriptor(id: "BQD001",
title: "Error creating bigquery mapper",
messageFormat: "{0} {1}",
category: "BigQueryMapperGenerator",
DiagnosticSeverity.Error,
isEnabledByDefault: true);
context.ReportDiagnostic(Diagnostic.Create(descriptor, null, e.Message, e.StackTrace));
}
}
}
static IEnumerable<ClassToGenerate> GetTypesToGenerate(Compilation compilation,
IEnumerable<ClassDeclarationSyntax> classes,
CancellationToken ct)
{
var columnAttributeSymbol =
compilation.GetTypeByMetadataName("System.ComponentModel.DataAnnotations.Schema.ColumnAttribute");
foreach (var #class in classes)
{
Debug.WriteLine($"Checking class {#class}");
ct.ThrowIfCancellationRequested();
var semanticModel = compilation.GetSemanticModel(#class.SyntaxTree);
if (semanticModel.GetDeclaredSymbol(#class) is not INamedTypeSymbol classSymbol)
continue;
var info = new ClassToGenerate(classSymbol, new());
foreach (var member in classSymbol.GetMembers())
{
if (member is IPropertySymbol propertySymbol)
{
if (propertySymbol.DeclaredAccessibility == Accessibility.Public)
{
if (propertySymbol.SetMethod is not null)
{
var columnName = propertySymbol.Name;
var columnAttribute = propertySymbol.GetAttributes().FirstOrDefault(a =>
a.AttributeClass!.ToDisplayString() == "Column");
if (columnAttribute is not null)
{
if (!columnAttribute.ConstructorArguments.IsDefaultOrEmpty)
{
var nameArg = columnAttribute.ConstructorArguments.First();
if (nameArg.Value is string name)
{
columnName = name;
}
}
}
info.Properties.Add((columnName, propertySymbol));
}
}
}
}
yield return info;
}
}
static string GeneratePartialClass(ClassToGenerate c)
{
var sb = new StringBuilder();
sb.Append($#"// <auto-generated/>
namespace {c.RowClass.ContainingNamespace.ToDisplayString()}
{{
public partial class {c.RowClass.Name} : BigQueryMapping.IBigQueryGenerated<{c.RowClass.Name}>
{{
public static {c.RowClass.Name} FromBigQueryRow(Google.Cloud.BigQuery.V2.BigQueryRow row)
{{
return new {c.RowClass.Name}
{{");
foreach (var (columnName, property) in c.Properties)
{
// would like to check if key exists but don't see any sort of ContainsKey implemented on BigQueryRow
var tempName = $"___{property.Name}";
var basePropertyType = property.Type.WithNullableAnnotation(NullableAnnotation.None).ToDisplayString();
if (basePropertyType.EndsWith("?"))
{
basePropertyType = basePropertyType.Substring(default, basePropertyType.Length - 1);
}
sb.Append($#"
{property.Name} = row[""{columnName}""] is {basePropertyType} {tempName} ? {tempName} : default,");
}
sb.Append($#"
}};
}}
}}
}}");
return sb.ToString();
}
private record struct ClassToGenerate(INamedTypeSymbol RowClass,
List<(string ColumnName, IPropertySymbol Property)> Properties);
public const string Attribute = /* lang=csharp */ #"// <auto-generated/>
namespace BigQueryMapping {
[System.AttributeUsage(System.AttributeTargets.Class)]
public class BigQueryMappedAttribute : System.Attribute
{
}
}";
public const string Interface = /* lang=csharp */ #"// <auto-generated/>
namespace BigQueryMapping {
public interface IBigQueryGenerated<TRow> {
static TRow FromBigQueryRow(Google.Cloud.BigQuery.V2.BigQueryRow row) => throw new System.NotImplementedException();
}
}";
}
I have tried this with both System.ComponentModel.DataAnnotations.Schema.ColumnAttribute and a custom attribute injected via context.RegisterPostInitializationOutput to similar results. I have also tried rewriting this to use ISourceGenerator instead of IIncrementalGenerator and gotten the same behavior. Am wondering what I need to do to get columnAttribute loading correctly.
Thanks for any help in advance
It's hard to psychic debug the code but this does jump out:
var columnAttribute = propertySymbol.GetAttributes().FirstOrDefault(a =>
a.AttributeClass!.ToDisplayString() == "Column");
I'd guess the class here would be the fully qualified name. You already fetched the ColumnAttribute type earlier, so what'd be even better is to compare the AttributeClass to that type rather than doing string checks like this.
As a semi-related comment, if you're looking for types/members that are annotated with a specific attribute, rather than doing it yourself we have SyntaxValueProvider.ForAttributeWithMetadataName which is pretty heavily optimized to reduce the performance impact on your Visual Studio. It requires 17.3 or higher, but as long as you're OK with that it'll generally help performance.
I have had a static version of this type of code working in a static version. However the API calls were just incredibly slow. I am trying to move to asynchronous now that C# 7 supports console async tasks (where I add code to connect to my DB and store data. I want to see this code output on the console to ensure it's working so I can assign variables for loading. I can't seem to figure out how to access the list from main. Here is the code I have so far:
Wrapper (or C# library):
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace AlphaVantageApiWrapper
{
public static class AlphaVantageApiWrapper
{
public static async Task<AlphaVantageRootObject> GetTechnical(List<ApiParam> parameters, string apiKey)
{
var stringRequest = parameters.Aggregate(#"https://www.alphavantage.co/query?", (current, param) => current + param.ToApiString());
stringRequest += "&apikey=" + apiKey;
var apiData = await CallAlphaVantageApi(stringRequest);
var technicalsObject = new AlphaVantageRootObject
{
MetaData = new MetaData
{
Function = parameters.FirstOrDefault(x => x.ParamName.Equals("function"))?.ParamValue ?? "NA?",
Interval = parameters.FirstOrDefault(x => x.ParamName.Equals("interval"))?.ParamValue ?? "NA?",
SeriesType = parameters.FirstOrDefault(x => x.ParamName.Equals("series_type"))?.ParamValue ?? "NA?",
Symbol = parameters.FirstOrDefault(x => x.ParamName.Equals("symbol"))?.ParamValue ?? "NA?"
},
TechnicalsByDate = apiData.Last.Values().OfType<JProperty>().Select(x => new TechnicalDataDate
{
Date = Convert.ToDateTime(x.Name),
Data = x.Value.OfType<JProperty>().Select(r => new TechnicalDataObject
{
TechnicalKey = r.Name,
TechnicalValue = Convert.ToDouble(r.Value.ToString())
}).ToList()
})
.ToList()
};
return technicalsObject;
}
public class ApiParam
{
public string ParamName;
public string ParamValue;
public ApiParam(string paramNameIn, string paramValueIn)
{
ParamName = paramNameIn;
ParamValue = paramValueIn;
}
public string ToApiString()
{
return $"&{ParamName}={ParamValue}";
}
}
public static string ToDescription(this Enum enumeration)
{
var type = enumeration.GetType();
var memInfo = type.GetMember(enumeration.ToString());
if (memInfo.Length <= 0) return enumeration.ToString();
var attrs = memInfo[0].GetCustomAttributes(typeof(EnumDescription), false);
return attrs.Length > 0 ? ((EnumDescription)attrs[0]).Text : enumeration.ToString();
}
public static async Task<JObject> CallAlphaVantageApi(string stringRequest)
{
try
{
using (var client = new HttpClient())
{
var res = await client.GetStringAsync(stringRequest);
return JsonConvert.DeserializeObject<JObject>(res);
}
}
catch (Exception e)
{
//fatal error
return null;
}
}
public class AlphaVantageRootObject
{
public MetaData MetaData;
public List<TechnicalDataDate> TechnicalsByDate;
}
public class MetaData
{
public string Function;
public string Interval;
public string SeriesType;
public string Symbol;
}
public class TechnicalDataDate
{
public DateTime Date;
public List<TechnicalDataObject> Data;
}
public class TechnicalDataObject
{
public string TechnicalKey { get; set; }
public double TechnicalValue { get; set; }
}
public class EnumDescription : Attribute
{
public string Text { get; }
public EnumDescription(string text)
{
Text = text;
}
}
public enum AvFuncationEnum
{
[EnumDescription("SMA")] Sma,
[EnumDescription("EMA")] Ema,
[EnumDescription("MACD")] Macd,
[EnumDescription("STOCH")] Stoch,
[EnumDescription("RSI")] Rsi,
}
public enum AvIntervalEnum
{
[EnumDescription("1min")] OneMinute,
[EnumDescription("5min")] FiveMinutes,
[EnumDescription("15min")] FifteenMinutes,
[EnumDescription("30min")] ThirtyMinutes,
[EnumDescription("60min")] SixtyMinutes,
[EnumDescription("daily")] Daily,
[EnumDescription("weekly")] Weekly,
[EnumDescription("monthly")] Monthly
}
public enum AvSeriesType
{
[EnumDescription("close")] Close,
[EnumDescription("open")] Open,
[EnumDescription("high")] High,
[EnumDescription("low")] Low,
}
}
}
`
The c# async main task (which obviously isn't working)...
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace AlphaVantageApiWrapper.Test
{
public static class AlphaVantageApiDbLoader
{
public static async Task Main(string[] args)
{
var API_KEY = "EnterAPIHERE";
var StockTickers = new List<string> { "AAPL" }; //eventualy becomes a list pulled in from the DB for processing
foreach (var ticker in StockTickers)
{
var parameters = new List<AlphaVantageApiWrapper.ApiParam>
{
new AlphaVantageApiWrapper.ApiParam("function", AlphaVantageApiWrapper.AvFuncationEnum.Sma.ToDescription()),
new AlphaVantageApiWrapper.ApiParam("symbol", ticker),
new AlphaVantageApiWrapper.ApiParam("interval", AlphaVantageApiWrapper.AvIntervalEnum.Daily.ToDescription()),
new AlphaVantageApiWrapper.ApiParam("time_period", "5"),
new AlphaVantageApiWrapper.ApiParam("series_type", AlphaVantageApiWrapper.AvSeriesType.Open.ToDescription()),
};
//Start Collecting SMA values
var SMA_5 = await AlphaVantageApiWrapper.GetTechnical(parameters, API_KEY);
///var SMA_5Result = AlphaVantageApiWrapper.TechnicalDataObject() // can't all method error just want values fron list
parameters.FirstOrDefault(x => x.ParamName == "time_period").ParamValue = "20";
var SMA_20 = await AlphaVantageApiWrapper.GetTechnical(parameters, API_KEY);
parameters.FirstOrDefault(x => x.ParamName == "time_period").ParamValue = "50";
var SMA_50 = await AlphaVantageApiWrapper.GetTechnical(parameters, API_KEY);
parameters.FirstOrDefault(x => x.ParamName == "time_period").ParamValue = "200";
var SMA_200 = await AlphaVantageApiWrapper.GetTechnical(parameters, API_KEY);
//Change function to EMA
//Change function to RSI
//Change function to MACD
}
}
}
}
Any help would be greatly appreciated! I know the code runs in the background, I just can't seem to get it to a point to view it on the console screen. Eventually I would assign the symbol, date, value returned variable and read these to a DB. I'm used to using DataTables, but the async and .ToList is new to me. Thanks!!
A third party is giving me something similar to the below. When I know the key (such as easyField) getting the value is easy. Below I write it in the console. However the third party gave me json that uses random keys. How do I access it?
{
var r = new Random();
dynamic j = JsonConvert.DeserializeObject(string.Format(#"{{""{0}"":""hard"", ""easyField"":""yes""}}", r.Next()));
Console.WriteLine("{0}", j["easyField"]);
return;
}
You can use reflection with JSON.NET! It will give you the keys of your fields.
Try it online: Demo
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
public class Program
{
public IEnumerable<string> GetPropertyKeysForDynamic(dynamic jObject)
{
return jObject.ToObject<Dictionary<string, object>>().Keys;
}
public void Main()
{
var r = new Random();
dynamic j = JsonConvert.DeserializeObject(string.Format(#"{{""{0}"":""hard"", ""easyField"":""yes""}}", r.Next()));
foreach(string property in GetPropertyKeysForDynamic(j))
{
Console.WriteLine(property);
Console.WriteLine(j[property]);
}
}
}
Edit:
An even simpler solution:
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
public class Program
{
public void Main()
{
var r = new Random();
dynamic j = JsonConvert.DeserializeObject(string.Format(#"{{""{0}"":""hard"", ""easyField"":""yes""}}", r.Next()));
foreach(var property in j.ToObject<Dictionary<string, object>>())
{
Console.WriteLine(property.Key + " " + property.Value);
}
}
}
This is what I had used in my project to get fields and values of a class:
public static List<KeyValuePair> ClassToList(this object o)
{
Type type = o.GetType();
List<KeyValuePair> vals = new List<KeyValuePair>();
foreach (PropertyInfo property in type.GetProperties())
{
if (!property.PropertyType.Namespace.StartsWith("System.Collections.Generic"))
{
vals.Add(new KeyValuePair(property.Name,(property.GetValue(o, null) == null ? "" : property.GetValue(o, null).ToString()))
}
}
return sb.ToString();
}
Note that the reason I was checking !property.PropertyType.Namespace.StartsWith("System.Collections.Generic") as It was causing infinte loops in entity models and if that is not the case you can remove the if condition.
Sorry for asking such a simple question but I lost really long time trying to solve this. At the end, I decide to ask you.
Let's start with the code base :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace Navigation.Helpers
{
public static class NavigationBarSE
{
public static MvcHtmlString RenderNavigationBarSE(this HtmlHelper helper, String[] includes)
{
return new MvcHtmlString("Y U no Work??");
//NavTypeSE res = new NavTypeSE(includes);
//String ress = res.toString();
//return new MvcHtmlString(ress);
}
}
}
In the original form, this helper needs to return a String that produced by the NavTypeSE class. But in the end, to get a result, I only want it to return a String for me... But it didn't do that...
Before you ask, I can say that,
<add namespace="Navigation.Helpers"/>
exists in my Web.config file in Views folder.
For detailed information, my NavTypeSE class as below :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Navigation.Helpers
{
//Creates a Navigation Menu Type which includes Previous, Next and Validate Buttons
public class NavTypeSE
{
Boolean pr, nt, vld;
Boolean Previous { get; set; }
Boolean Next { get; set; }
Boolean Validate { get; set; }
public NavTypeSE(Boolean Previous, Boolean Next, Boolean Validate)
{
this.pr = Previous;
this.nt = Next;
this.vld = Validate;
}
public NavTypeSE() { }
public NavTypeSE(String[] inc)
{
for(int i=0; i<inc.Length; i++)//foreach (String s in inc)
{
String s = inc[i]; // Don't need for foreach method.
if (s.Equals("previous")||s.Equals("Previous"))
{
this.pr = true;
}
else if (s.Equals("next") || s.Equals("Next"))
{
this.nt = true;
}
else if (s.Equals("validate") || s.Equals("Validate"))
{
this.vld = true;
}
else
{
this.pr = false; this.nt = false; this.vld = false;
}
}
public String toString()
{
return "Previous: " + this.pr + ", Next: " + this.nt + ", Validate: " + this.vld;
}
}
}
Also, in my View, I call this Helper like below :
#{
String[] str = new String[] { "Previous", "next", "Validate" };
Html.RenderNavigationBarSE(str);
}
This is just a base for a project. And I'm starter level in both C# and ASP.NET MVC Platform. Sorry for spending your time.
Your RenderNavigationBarSE writes nothing into the Response just returns a MvcHtmlString.
So you need to put an # before the method call to tell Razor engine that you want to write the returned MvcHtmlString into the response (otherwise inside a code block it just executes your method and throws away the returned value)
#{
String[] str = new String[] { "Previous", "next", "Validate" };
}
#Html.RenderNavigationBarSE(str);
You can read more about the Razor syntax:
Introduction to ASP.NET Web Programming Using the Razor Syntax (C#)
There is also a C# Razor Syntax Quick Reference
I'm trying to create a CSV extension method for my enumerable list and I'm stumped. Here's how I created my simple enumerated list:
var CAquery = from temp in CAtemp
join casect in CAdb.sectors
on temp.sector_code equals casect.sector_code
select new
{
CUSIP = temp.equity_cusip,
CompName = temp.company_name,
Exchange = temp.primary_exchange
};
CAquery.WriteToCSVFile();
This is what I have done so far in creating an extension method (which I think is wrong):
public static class CSVExtensions
{
public static void WriteToCSVFile(this IEnumerable<T> myList)
{
Do you see what I'm doing wrong?
You have to specify the generic type parameter in the method signature:
public static class CSVExtensions
{
public static void WriteToCSVFile<T>(this IEnumerable<T> myList)
{
//your code here
}
}
Are you truly trying to write an extension method that should work on any IEnumerable<T> or is your type more specific? If the later is the case you should replace T with the type you want to support (or add sufficient constraints).
Edit:
In light of comments - you should project to a class instead of an anonymous type in your query - then you can use an extension method for this particular type, i.e.:
class CompanyTicker
{
public string CUSIP {get;set;}
public string CompName {get;set;}
public string Exchange {get;set;}
}
Now your query can be:
var CAquery = from temp in CAtemp
join casect in CAdb.sectors
on temp.sector_code equals casect.sector_code
select new CompanyTicker
{
CUSIP = temp.equity_cusip,
CompName = temp.company_name,
Exchange = temp.primary_exchange
};
And your extension method (which now doesn't need to be generic) becomes:
public static class CSVExtensions
{
public static void WriteToCSVFile(this IEnumerable<CompanyTicker> myList)
{
//your code here
}
}
It is possible to do what you are trying to do using reflection. The performance is going to be somewhat worse than if you write non-generic code however.
Here is a complete code sample:
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
class Program
{
static void Main(string[] args)
{
var seq =
Enumerable.Range(0, 100)
.Select(i => new { Name = "Item" + i, Value = i })
;
seq.WriteCsv(Console.Out);
Console.ReadLine();
}
}
public static class CsvExtension
{
public static void WriteCsv<T>(this IEnumerable<T> seq, TextWriter writer)
{
var type = typeof(T);
MethodInfo[] getters = type.GetProperties().Select(pi => pi.GetGetMethod()).ToArray();
// only supporting simple properties
// indexer properties will probably fail
var args = new object[0];
foreach (var item in seq)
{
for (int i = 0; i < getters.Length; i++)
{
if (i != 0)
writer.Write(",");
Object value = getters[i].Invoke(item, args);
var str = value.ToString();
if (str.Contains(",") || str.Contains("\""))
{
var escaped = str.Replace("\"", "\\\"");
writer.Write("\"");
writer.Write(escaped);
writer.Write("\"");
}
else
{
writer.Write(str);
}
}
writer.WriteLine();
}
}
}