0
public ActionResult AddComplianceForm(string TemplateName)
{
}

In this ASP.net MVC5 application there is a folder Templates that contains a bunch of different classes that all have different TemplateName attributes. The first part of this method needs to find the class that has a TemplateName matching the string passed in. I then need to create an instance of whatever template matched. I am very new to working with attributes in C# so help would be very appreciated. I mostly need to know how to access that folder of classes in the program to look into it.

Bender Bending
  • 729
  • 8
  • 25
tiltcode12
  • 27
  • 5

3 Answers3

2

What you are trying to do is called "Reflection" in C#.

Below is the link to another answer that shows how to get all the classes in a namespace (I'm assuming that the physical folder implies the use of a unique namespace for the classes contained in the folder.)

Link to StackOverflow answer

**Btw you should look up reflection performance and see if it makes sense in your case. You may want to use a factory pattern instead.

Dominik Holland
  • 368
  • 3
  • 6
0

This will work:

public class HomeController : Controller
{
    public ActionResult AddComplianceForm(string TemplateName)
    {
        Assembly assembly = Assembly.Load("Testy20161006");                     //assembly name
        Type t = assembly.GetType("Testy20161006.Templates." + TemplateName);   //namespace + class name
        Object obj = (Object)Activator.CreateInstance(t);

        return View();
    }
kblau
  • 2,094
  • 1
  • 8
  • 20
0

Don't just use reflection

The typical way someone would deal with this is with Reflection and run-time type discovery/binding. However, this is probably a poor starting point in this exact situation. The template name is passed in as an action argument, presumably through binding to a value in the request string, and you don't want c# code that will instantiate whatever class is passed in from the web!!! That would be a serious security issue known as an insecure direct object reference.

Create a list

To mitigate the risk, the proper approach is to check the argument against a whitelist. Well, if we have a white list already, we may as well associate each item in the list with a lambda expression that returns the object you want.

class MyController
{
    static private readonly Dictionary<string,Func<BaseTemplate>> _templateList = new Dictionary<string,Func<BaseTemplate>>();

    static MyController() 
    {
        _templateList.Add("ATemplate",         () => return new ATemplate());
        _templateList.Add("SomeOtherTemplate", () => return new SomeOtherTemplate());
        _templateList.Add("JustOneMore",       () => return new JustOneMore());
    }

    public ActionResult AddComplianceForm(string TemplateName)
    {
        BaseTemplate template;
        try
        {
            template = _templateList[TemplateName]();
        }
        catch (KeyNotFoundException exception)
        {
            RedirectToAction("MyController", "InvalidTemplateError");
        }

        DoSomethingWithTemplate(template);
    }

}

Create the list using Reflection

But what if you have a crap ton of templates? You don't want to hard code all those dictionary entries, right?

Well, you could tag each of the templates with a custom attribute, e.g. [TemplateAttribute], and then populate the list this way:

        foreach (Assembly b in AppDomain.CurrentDomain.GetAssemblies())
        {
            foreach (Type t in b.GetTypes())
            {
                var a = Attribute.GetCustomAttribute(t, typeof(TemplateAttribute));
                if (a != null)
                {
                    var localType = t; //Avoid closure on loop variable
                    _templateList.Add(t.Name, () => Activator.CreateInstance(localType) as BaseTemplate);
                }
            }
        }

This will automatically iterate through all the types that are loaded for your application and find the ones with the TemplateAttribute. Only those types will be allowed in the action argument.

Notes:

In these examples I assume all of your templates inherit from a BaseTemplate, but if they have no ancestor in common (not recommended) I guess you could just use object.

In these examples, I store the list and implement the code in the controller, but if you are going for well-structured code you should consider moving all that stuff into some sort of factory class and just pass the string in from the controller.

John Wu
  • 50,556
  • 8
  • 44
  • 80