how to pass class object to string.format - c#

I am trying to pass object as a parameter to String.Format but it's not working throwing zero-based index exception as it's not getting object property to allocate.
How do I pass class object to String.Format?
class TestPrimaryObject
{
public string userId { get; set; }
public string param2 { get; set; }
public string param3 { get; set; }
public string param4 { get; set; }
}
I am passing object of above class to String.Format. Below are the values of object property:
TestPrimaryObject objTest = new TestPrimaryObject();
objTest.userId = "test1";
objTest.param2 = "test2";
objTest.param3 = "test3";
objTest.param4 = "test4";
string formatedString = string.Format("http://example.com?param3={0}&userId={1}", objTest);
Parameter might vary according to URL
How do I achieve this?

Try this:
var objTest = new TestPrimaryObject {
param1 = "test1",
param2 = "test2",
param3 = "test3",
param4 = "test4"
};
var path = "http://example.com?param2={0}&param4={1}";
//HttpUtility from System.Web;
var keys = HttpUtility.ParseQueryString(new Uri(path).Query).AllKeys
.Select(x => objTest.GetType().GetProperty(x).GetValue(objTest)).ToArray();
var formatedString = string.Format(path, keys);
//output - http://example.com?param2=test2&param4=test4

The thing you want to achieve is not possible. I bet that you would like to get the behaviour, such that {0} and {1} would get replaced by param1 and param2 of an object. First of all, if this were possible, how could compiler know which properties you wanted to pass? Secondly, what if you passed 2 objects with 3 placeholders?
class Object1 {
public int Property1 { get; set; }
public int Property2 { get; set; }
}
Now imagine such scenario:
string.Format("{0} {1} {2}", new Object1(), new Object1());
Should the compiler take 2 properties of first object and one from the second or the other way round?
You have to explicitly pass properties.
string formatedString = string.Format("http://example.com?var1={0}&var2={1}", objTest.param1, objTest.param2);

Why don't you directly assign the values to url
objTest.param1 = "test1";
objTest.param2 = "test2";
objTest.param3 = "test3";
objTest.param4 = "test4";
string sURL = "http://example.com?var1="+objTest.param1+"&var2="+objTest.param2
//string formatedString = string.Format("http://example.com?var1={0}&var2={1}", type1);

the error you're getting is because you want to show 2 parameters {0} {1} and you provide just 1 argument.
If you want the output to display the properties : I would recommend to override the ToString() function of the class. and work with a strategy pattern to change the behaviour accordingly.
Note that this is not an elegant solution.
void Main()
{
var test= new TestPrimaryObject(){ param1="param1", param2="param2", param3="Param3", param4="param4"};
Console.WriteLine(test);
test.Formatter = new Param3And4();
Console.WriteLine(test);
test.Formatter = new Param1And2();
Console.WriteLine(test);
}
public class TestPrimaryObject
{
public TestPrimaryObject(){
Formatter= new Param1And2(); // set default here
}
public string param1 { get; set; }
public string param2 { get; set; }
public string param3 { get; set; }
public string param4 { get; set; }
public IStringOverride<TestPrimaryObject> Formatter {get;set;}
public override string ToString(){
return Formatter.Format(this);
}
}
public interface IStringOverride<T>{
string Format(T arg);
}
public class Param1And2 : IStringOverride<TestPrimaryObject>{
public string Format(TestPrimaryObject arg){
return string.Format("http://example.com?var1={0}&var2={1}", arg.param1, arg.param2);
}
}
public class Param3And4 : IStringOverride<TestPrimaryObject>{
public string Format(TestPrimaryObject arg){
return string.Format("http://example.com?var1={0}&var2={1}", arg.param3, arg.param4);
}
}

You can't achieve what you are trying to do with just a single call to String.Format. Since the number of parameters will vary, you need to conditionally be able to generate your url with an arbitrary number of parameters. You can create a method to do so:
publc string FormatUrl(string url, TestPrimaryObject objTest)
{
StringBuilder sb = new StringBuilder();
if (!String.IsNullOrEmpty(objTest.userId))
{
sb.Append($"userId={objTest.userId}");
}
if (!String.IsNullOrEmpty(objTest.param2))
{
if (sb.Length > 0)
sb.Append("&");
sb.Append($"param2={objTest.param2}");
}
if (!String.IsNullOrEmpty(objTest.param3))
{
if (sb.Length > 0)
sb.Append("&");
sb.Append($"param3={objTest.param3}");
}
if (!String.IsNullOrEmpty(objTest.param4))
{
if (sb.Length > 0)
sb.Append("&");
sb.Append($"param4={objTest.param4}");
}
return $"{url}?{sb.ToString()}";
}
And call it like so:
TestPrimaryObject objTest = new TestPrimaryObject();
objTest.userId = "test1";
objTest.param2 = "test2";
objTest.param3 = "test3";
objTest.param4 = "test4";
string url = FormatUrl("http://www.example.com/", objTest);
It will return:
http://www.example.com/?userId=test1&param2=test2&param3=test3&param4=test4

Related

How to add mapper to JavaScriptSerializer().Serialize to get custom(minified) json without using [JsonProperty("")]?

I want to customize JavaScriptSerializer().Serialize by adding mapping.
Make it clear:
Think we have class:(as it comes from other library I can't modify this class, so I can not use [JsonProperty("")] )
class Address
{
public string Number { get; set; }
public string Street { get; set; }
public string City { get; set; }
public string Country { get; set; }
}
output should be:
{
"NMB" : "No 25",
"STR" : "Main Street",
"CTY" : "Matale",
"CNT" : "Sri Lanka"
}
How can I achieve mapping during JavaScriptSerializer().Serialize(Address_Object);?
IMHO, the simpliest way would be to create an util to serialize, you can use a net serializer, or a Newtonsoft.Json one
var addr = new Address {....}
string json = SerializeAddress(addr);
public string SerializeAddress(Address address)
{
var addr = new
{
NMB = address.Number,
STR = address.Street,
CTY = address.City,
CNT = address.Country
};
return Newtonsoft.Json.JsonConvert.SerializeObject(addr, Newtonsoft.Json.Formatting.Indented);
//Or
return System.Text.Json.JsonSerializer.Serialize(addr);
}

Email Generator iteration through a list

I've written a function that generates an HTML email and fills it with information from a database.
I've been trying to iterate through a list, but can't seem to get the function to be generic and run throught the Items list.
Here is the Email Generator function. It is fairly generic, so that it can be used in a wide variety of email templates.
public interface IMailObject
{
string Subject { get; }
}
public interface IEmailGenerator
{
MailMessage generateEmail(IMailObject mailObject, string htmlTemplate, string textTemplate);
}
public class EmailGenerator : IEmailGenerator, IRegisterInIoC
{
private string mergeTemplate(string template, object obj)
{
Regex operationParser = new Regex(#"\$(?:(?<operation>[\w\-\,\.]+)\x20)(?<value>[\w\-\,\.]+)\$", RegexOptions.Compiled);
Regex valueParser = new Regex(#"\$(?<value>[\w\-\,\.]+)\$", RegexOptions.Compiled);
var operationMatches = operationParser.Matches(template).Cast<Match>().Reverse().ToList();
foreach (var match in operationMatches)
{
string operation = match.Groups["operation"].Value;
string value = match.Groups["value"].Value;
var propertyInfo = obj.GetType().GetProperty(value);
if (propertyInfo == null)
throw new TillitException(String.Format("Could not find '{0}' in object of type '{1}'.", value, obj));
object dataValue = propertyInfo.GetValue(obj, null);
if (operation == "endforeach")
{
string foreachToken = "$foreach " + value + "$";
var startIndex = template.LastIndexOf(foreachToken, match.Index);
var templateBlock = template.Substring(startIndex + foreachToken.Length, match.Index - startIndex - foreachToken.Length);
var items = (IEnumerable) value;
string blockResult = "";
foreach (object item in items)
{
blockResult += mergeTemplate(templateBlock, item);
}
template = template.Remove(startIndex, match.Index - startIndex).Insert(startIndex, blockResult);
}
}
var valueMatches = valueParser.Matches(template).Cast<Match>().Reverse().ToList();
foreach (var match in valueMatches)
{
string value = match.Groups["value"].Value;
var propertyInfo = obj.GetType().GetProperty(value);
if (propertyInfo == null)
throw new Exception(String.Format("Could not find '{0}' in object of type '{1}'.", value, obj));
object dataValue = propertyInfo.GetValue(obj, null);
template = template.Remove(match.Index, match.Length).Insert(match.Index, dataValue.ToString());
}
return template;
}
public MailMessage generateEmail(IMailObject mailObject, string htmlTemplate, string textTemplate)
{
var mailMessage = new MailMessage();
mailMessage.IsBodyHtml = true;
mailMessage.Subject = mailObject.Subject;
mailMessage.BodyEncoding = Encoding.UTF8;
// Create the Plain Text version of the email
mailMessage.Body = this.mergeTemplate(textTemplate, mailObject);
// Create the HTML version of the email
ContentType mimeType = new System.Net.Mime.ContentType("text/html");
AlternateView alternate = AlternateView.CreateAlternateViewFromString(this.mergeTemplate(htmlTemplate, mailObject), mimeType);
mailMessage.AlternateViews.Add(alternate);
return mailMessage;
}
}
And here is a case of the message data:
public class MessageData : IMailObject
{
public string Property1 { get; private set; }
public string Property2 { get; private set; }
public string Property3 { get; private set; }
public string Property4 { get; private set; }
public string Property5 { get; private set; }
public string Property6 { get; private set; }
public string Subject
{
get { return this.Property1 + DateTime.Now.ToShortDateString(); }
}
public List<MessageItemData> Items { get; private set; }
public MessageData(string property1, string property2, string property3, DateTime property4, string property7, string property8, DateTime property9, DateTime property10, int property11, double property12, string property5, string property6)
{
this.Property1 = property1;
this.Property2 = property2;
this.Property3 = property3;
this.Property4 = property4.ToShortDateString();
this.Property5 = property5;
this.Property6 = property6;
this.Items = new List<MessageItemData>();
this.Items.Add(new MessageItemData(property7, property8, property9, property10, property11, property12));
}
}
public class MessageItemData
{
public string Property7 { get; private set; }
public string Property8 { get; private set; }
public string Property9 { get; private set; }
public string Property10 { get; private set; }
public int Property11 { get; private set; }
public double Property12 { get; private set; }
public MessageItemData( string property7, string property8, DateTime property9, DateTime property10, int property11, double property12)
{
this.Property7 = property7;
this.Property8 = property8;
this.Property9 = property9.ToShortDateString();
this.Property10 = property10.ToShortDateString();
this.Property11 = property11;
this.Property12 = property12;
}
}
The function works when there is only one set of elements being used. If we use the MessageData class as an example. All the information will be replaced correctly, but I'm wanting to improve the email generator function, because this particular MessageData class has a list of objects, where Property7 to Property12 will be replaced multiple times.
The function is started at: if (operation == "endforeach"), but I need some help to improve it so that it runs through the: var items = (IEnumerable) value;, so that the function returns TemplateHeader + TemplateItem + TemplateItem + ...however many TemplateItems there are + TemplateFooter. It currently will only return TemplateHeader + TemplateItem + TemplateFooter, even though there are multiple items in the list, it will only return the first item.
In this case I'm assuming I need to get the List Items. I've been trying to implement it into the EmailGenerator just below:
var items = (IEnumerable) value;
with the code:
if (propertyInfo.PropertyType == typeof(List<>))
{
foreach (var item in items)
{
Console.WriteLine(item);
}
}
Console.WriteLine is just for testing purposes, to see if I get any values in Debug(which I'm currently getting null)
Assuming the type of the Items property is the same across all instances you may want to try using IsInstanceOfType instead. And then get the value of the property via the GetValue method. Reflection can be confusing at times ;) but hopefully, it is what you are looking for.
var data = new MessageData("a", "b", "c", DateTime.Now, "d", "e", DateTime.Now, DateTime.Now, 1, 2, "f", "g");
data.Items.Add(new MessageItemData("7", "8", DateTime.Now, DateTime.Now, 11, 12));
data.Items.Add(new MessageItemData("71", "81", DateTime.Now, DateTime.Now, 111, 112));
var dataType = data.GetType();
foreach (var propertyInfo in dataType.GetProperties())
{
if (propertyInfo.PropertyType.IsInstanceOfType(data.Items))
{
foreach (var item in (List<MessageItemData>)propertyInfo.GetValue(data))
{
Console.WriteLine(item);
}
}
}

Reflection of object properties

i have this code
public class ParameterOrderInFunction : Attribute
{
public int ParameterOrder { get; set; }
public ParameterOrderInFunction(int parameterOrder)
{
this.ParameterOrder = parameterOrder;
}
}
public interface IGetKeyParameters
{
}
public class Person: IGetKeyParameters
{
[ParameterOrderInFunction(4)]
public string Age { get; set; }
public string Name { get; set; }
[ParameterOrderInFunction(3)]
public string Address { get; set; }
[ParameterOrderInFunction(2)]
public string Language { get; set; }
[ParameterOrderInFunction(1)]
public string City { get; set; }
public string Country { get; set; }
}
class Program
{
static void Main(string[] args)
{
Person person = new Person();
person.Address = "my address";
person.Age = "32";
person.City = "my city";
person.Country = "my country";
Test t = new Test();
string result = t.GetParameter(person);
//string result = person.GetParameter();
Console.ReadKey();
}
}
public class Test
{
public string GetParameter(IGetKeyParameters obj)
{
string[] objectProperties = obj.GetType()
.GetProperties()
.Where(p => Attribute.IsDefined(p, typeof(ParameterOrderInFunction)))
.Select(p => new
{
Attribute = (ParameterOrderInFunction)Attribute.GetCustomAttribute(p, typeof(ParameterOrderInFunction), true),
PropertyValue = p.GetValue(this) == null ? string.Empty : p.GetValue(this).ToString()
})
.OrderBy(p => p.Attribute.ParameterOrder)
.Select(p => p.PropertyValue)
.ToArray();
string keyParameters = string.Join(string.Empty, objectProperties);
return keyParameters;
}
}
What i am trying to do is to get properties values as one string with some order .
it work fine if i put the function GetParameter inside the Person class.
however, i want to use the function GetParameter with other class as well,
so i create empty interface.
Now i want that every object that is of type IGetKeyParameters can use the function.
but i am getting exception in the line:
PropertyValue = p.GetValue(this) == null ? string.Empty : p.GetValue(this).ToString()
You should change loading properties from this (that doesn't have such properties) to parameter object:
PropertyValue = p.GetValue(obj) == null ? string.Empty : p.GetValue(obj).ToString()
You are passing the wrong reference as parameter to the method, you need to pass the object which you used to get the type and properties, so change:
p.GetValue(this) // this means pass current instance of containing class i.e. Test
to:
p.GetValue(obj)
Your statement p.GetValue(this) currenly means to pass the current instance of class Test as parameter which is i am pretty sure not what you want.
in your example code.

Get all properties from a view model to display in array

I have a view model that have a lot of properties, and I created a custom attribute for send the data to hubspot, because hubspot need a specific nomenclature, then I need to create a method that have some king of iterator, that for every property that contain my custom attribute he put a specific output, here is the code:
public class CreateTrialUserHubspotViewModel {
[HubspotAttribute("firstname")]
[Display(Name = "First Name")]
[StringLength(50)]
[Required]
public string FirstName { get; set; }
[HubspotAttribute("lastname")]
[Display(Name = "Last Name")]
[StringLength(50)]
[Required]
public string LastName { get; set; }
[HubspotAttribute("jobtitle")]
[Display(Name = "Job title")]
[StringLength(50)]
[Required]
public string JobTitle { get; set; }
}
Now this is my custom attribute
[AttributeUsage(AttributeTargets.All)]
public class HubspotAttribute : System.Attribute {
public readonly string hubspotValue;
public HubspotAttribute(string value)
{
this.hubspotValue = value;
}
}
And then I need to create a method that take a viewmodel object and create my output, I need some suggest about how to do that, will be something like this:
private static RowValidation ValidateRowWithManifest<T>(CreateTrialUserHubspotViewModel trialUser) {
RowValidation validation = new RowValidation();
FieldInfo[] fields = typeof(T).GetPropertiesOfSomeWay;
foreach (DataType field in fields) {
output+=whatINeed
}
return validation;
}
}
The needed output will be like: [firstname:"pepe", lastname="perez", jobtitle"none"]. just calling that method will return all the data I need.
public string GetString<T>(T #object)
{
var output = new StringBuilder();
var type = typeof(T);
var properties = type.GetProperties();
foreach (var property in properties)
{
var attributes = property.GetCustomAttributes(typeof(HubspotAttribute), true);
if (attributes.Length == 0)
continue;
var name = ((HubspotAttribute)attributes[0]).hubspotValue;
var value = property.GetValue(#object) ?? "none";
output.AppendFormat("{0}:\"{1}\",", name, value);
}
var fields = output.ToString().TrimEnd(',');
return string.Format("[{0}]", fields);
}
If you are looking for something that will concatenate properties into a string that looks like a JSON string (and that would be a better way to handle it), you can use something like the following:
private static string CreateOutput(CreateTrialUserHubspotViewModel trialUser)
{
var properties = trialUser.GetType().GetProperties().Where(x => Attribute.IsDefined(x, typeof(HubspotAttribute))).ToList();
var values = properties.Select(x =>
{
var att = x.GetCustomAttribute(typeof(HubspotAttribute));
var key = ((HubspotAttribute)att).hubspotValue;
var val = x.GetValue(trialUser);
return $"{key}:{val}";
});
var sb = new StringBuilder();
values.ToList().ForEach(v =>
{
sb.Append(v);
if (values.Last() != v) sb.Append(',');
});
return sb.ToString();
}

Converting an array to object

I have 2 types of string: Mer and Spl
// Example
string testMer = "321|READY|MER";
string testSpl = "321|READY|SPL";
Then I will split them:
var splitMer = testMer.Split('|');
var splitSpl = testSpl.Split('|');
I have an object to save them
public class TestObject
{
public int id { get; set; }
public string status { get; set; }
public string type { get; set; }
}
Question: How to convert the Array into the TestObject?
var converted = new TestObject
{
id = int.Parse(splitMer[0]),
status = splitMer[1],
type = splitMer[2]
};
You will need to add some error checking.
var values = new List<string> { "321|READY|MER", "321|READY|SPL" };
var result = values.Select(x =>
{
var parts = x.Split(new [] {'|' },StringSplitOptions.RemoveEmptyEntries);
return new TestObject
{
id = Convert.ToInt32(parts[0]),
status = parts[1],
type = parts[2]
};
}).ToArray();
You just need to use object initializers and set your properties.By the way instead of storing each value into seperate variables, use a List.Then you can get your result with LINQ easily.
var splitMer = testMer.Split('|');
var testObj = new TestObject();
testObj.Id = Int32.Parse(splitMer[0]);
testObj.Status = splitMer[1];
testObj.type = splitMer[2];
How about adding a Constructor to your Class that takes a String as a Parameter. Something like this.
public class TestObject
{
public int id { get; set; }
public string status { get; set; }
public string type { get; set; }
public TestObject(string value)
{
var valueSplit = value.Split('|');
id = int.Parse(valueSplit[0]);
status = valueSplit[1];
type = valueSplit[2];
}
}
Usage:
TestObject tst1 = new TestObject(testMer);
TestObject tst2 = new TestObject(testSpl);

Categories

Resources