0

I know this question sounds like it could be a duplicate, but I could not find anything related to what I am about to describe.

I am trying to build an html email templating project. I have a viewmodel (only calling them that for clarity that they are simply classes with properties necessary for that email to pass the values that need to be replaced in the template)

 public class EmailViewModels
{
    public class AccountClosedEmailViewModel
    {
        public string Fullname { get; set; }
        public string ApplicationName { get; set; }
        public string Username { get; set; }
        public string RenewRegistrationUrl { get; set; }
    }

    public class AccountReopenedEmailViewModel
    {
        public string Fullname { get; set; }
        public string ApplicationName { get; set; }
        public string Username { get; set; }
        public string RenewRegistrationUrl { get; set; }
        public string CancelVerificationUrl { get; set; }
    }

    public class ContactFormEmailViewModel
    {
        public string Fullname { get; set; }
        public string ApplicationName { get; set; }
    }
    //rest removed for brevity
}

I want a method that takes an email view model, it could be any of those defined in the class above so I have a generic method to eliminate me having a BuildHtmlFromTemplate for each view model type.

public string BuildHtmlFromTemplate(templateFilePath, passing any one of the email viewmodels defined in the class)
{
    var temp = File.IO.ReadAllText(templateFilePath);
    temp.Replace("{username}", modelipassedin.username);
    //do rest
    return temp;

}

Is this possible?

12
  • what you want do inside BuildHtmlFromTemplate? why not add function for every class? Commented Dec 4, 2015 at 9:49
  • Well, you could have a parameter of type object... but we don't know what the method is meant to do - what the code looks like. You could use reflection to find the properties, for example. Commented Dec 4, 2015 at 9:50
  • @JonSkeet and using generic instead object BuildHtmlFromTemplate<T>(T o) Commented Dec 4, 2015 at 9:50
  • @Grundy: Well maybe - but only if that's actually useful, which we can't tell at the moment. Commented Dec 4, 2015 at 9:51
  • @JonSkeet, yep :-) need more info :-) Commented Dec 4, 2015 at 9:52

3 Answers 3

4

You can use reflection and generics like this

public string BuildHtmlFromTemplate<T>(templateFilePath, T o)
{
    var temp = File.IO.ReadAllText(templateFilePath);
    foreach(var prop in typeof(T).GetProperties()){
        temp = temp.Replace("{"+prop.Name+"}", prop.GetValue(o));
    }
    return temp;
}

Or without generics

public string BuildHtmlFromTemplate(templateFilePath, object o)
{
    var temp = File.IO.ReadAllText(templateFilePath);
    foreach(var prop in o.GetType().GetProperties()){
        temp = temp.Replace("{"+prop.Name+"}", prop.GetValue(o));
    }
    return temp;
}
Sign up to request clarification or add additional context in comments.

10 Comments

Generics bring nothing useful in this case.
@KubaWyrostek, yep, instead can use object and o.GetType() instead typeof(T)
Generics approach with typeof(T) might fail if you don't know the exact type at compile type (i.e. if passing a base class or object).
@Groo, added sample without generics
@Grundy very simple design. I've never seen something like the {"+prop.Name+"} what are the +'s for?
|
2

If you want to be able to access object properties by name, regardless of their type, then you can either:

  1. Use reflection, or

  2. Turn all classes into dictionaries.

To get the value of a property by its name using reflection, you would use something like:

public static object GetValue(this object @this, string property)
{
    return @this.GetType().GetProperty(property).GetValue(@this, null);
}

You could then use a regex to match and replace placeholders at once:

var inputHtml = @"<h1>{title}</h1> <p>{text}</p>";
var instance = new
{
    title = "some title",
    text = "some text"
};

// note: not much error checking here, and
// placeholder casing is important 

var regex = new Regex("{(.*?)}");
var outputHtml = regex.Replace(inputHtml, m =>
{
    var placeholder = m.Groups[1].Value;

    var prop = instance.GetType().GetProperty(placeholder);
    if (prop == null)
        return "";

    return prop.GetValue(instance).ToString();
});

For the second approach, you simply store your data as KeyValuePair<string, string> and do some simple string replacement.

5 Comments

interesting thought on dictionaries. I suppose I could have the placeholder as the key and the associated model property as its value and just loop through the pairs
@dinotom: yes, that's the most weakly typed solution, and avoids the need for reflection. But it also simplifies data entry (i.e. you could simply have a table with "name" and "value" columns to enter a bunch of values).
I was thinking more along the lines of having a method in the class return a dictionary where the property name as string is the key, and the property value as the value and pass that dictionary to the BuildHtmlMethod. i would have to adjust my templates so the placeholders exactly matched the the class property names. Will that work?
@dinotom: yes, but I don't see a point in doing that manually for each class. And the functionality doesn't seem like it belongs inside the actual classes. If you still want to have strongly typed classes, then use reflection and you're done. Also, if your templates have to be case insensitive, you can easily convert the property names to lower case while replacing, as long as everyone in the team is aware of the convention.
Grundy's answer (the second one, using an object) does a similar thing, and it's simpler than a regex (just change prop.Name into prop.Name.ToLower()), the only issue might be additional allocations on each Replace which shouldn't be an issue unless your templates are really large and have many placeholders.
-2
public class EmailViewModel
{
  public string ToHTMLString()
  {
     //And use reflection on the properties here.
  }
}

public class AccountClosedEmailViewModel:EmailViewModel    {
    public string Fullname { get; set; }
    public string ApplicationName { get; set; }
    public string Username { get; set; }
    public string RenewRegistrationUrl { get; set; }
}

public class AccountReopenedEmailViewModel:EmailViewModel{
    public string Fullname { get; set; }
    public string ApplicationName { get; set; }
    public string Username { get; set; }
    public string RenewRegistrationUrl { get; set; }
    public string CancelVerificationUrl { get; set; }
}

public class ContactFormEmailViewModel:EmailViewModel    {
    public string Fullname { get; set; }
    public string ApplicationName { get; set; }
}

Edit: And this way you can move some of the common properties in the base class in order to make each model a bit lighter.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.