Create data type from string to call generic method?

d. simic 41 Reputation points
2022-12-30T07:37:17.78+00:00

Hi Community,

I have two different applications (WebApi and client app). But both projects are in Visual Studio and use classes from a third common project (rcl). So the models (classes) I use in both projects are present only once in rcl.

When I call the WebApi from my Client-App, I pass model-name as a string. In the WebApi, I then need to convert this string to the real class (to populate my lists with data).

I have methods programmed generically in rcl and when I call the method I pass my model T. This is not a problem if I do it inside a project (WebApi or Client-Api).

My problem appears when I have to pass a model from the client to WebApi, because I can only do that as a string (model name).

I can then create an instance in WebApi using the model name (models are all known because they come from rcl project), but the difficulty is to pass the class /model itself to the generic methods! Here is an example of what I need to do:

WebApi get the Class/Model name T and schould use it here as model/class:

   DataTable tblRes = new DataTable("tbl");  
   tblRes = await mssql.GetData("SELECT ....");  
  
   List<T> list = new List<T>();  
   foreach (DataRow row in tblRes.Rows)  
   {  
        T item = mssql.GetItem<T>(row);  
        list.Add(item);  
    }  
  
    return await Task.FromResult(Results.Ok(list));  

So how do I convert my class/model name as T (which I get as a string from client api) to a real class, which I can then pass to a generic method. For example:

T item = mssql.GetItem<T>(row);  

Thanks

Developer technologies C#
0 comments No comments
{count} votes

4 answers

Sort by: Most helpful
  1. Viorel 122.5K Reputation points
    2022-12-30T11:09:37.05+00:00

    For example, if you put your code into a generic function:

    class MyClass  
    {  
        public void MyFunction<T>( ) where T : class, new()  
        {  
            . . .  
            List<T> list = new List<T>( );  
            . . .  
            T item = mssql.GetItem<T>(row);  
            . . .  
        }  
    }  
    

    then you can do something like this:

    string model_name = "...the model name including namespaces...";   
      
    var f = typeof( MyClass ).GetMethod( "MyFunction" );  
    var t = Type.GetType( model_name );  
    var gf = f.MakeGenericMethod( t );  
      
    MyClass obj = new MyClass( ); // or maybe 'obj = this'  
    gf.Invoke( obj, null ); // this executes 'MyFunction<T>( )'  
    

    You can adjust or simplify it according to details.

    0 comments No comments

  2. d. simic 41 Reputation points
    2022-12-30T16:44:35.667+00:00

    Thanks Viorel-1 for your suggestion.

    I have to explain it again a little more precisely because this was misunderstood.

    1. I have a function (WebApi) that gets as parameter the class name from client app
    public static async Task<IResult> GetMyData(string className)  
    {  
     .  
    }  
    
    1. in the function I then call a generic method that returns me the result:
      public static async Task<IResult> GetMyData(string className)  
      {  
        DataTable tblRes = new DataTable("tbl");  
        tblRes = await mssql.GetData("SELECT ....");  
    
        List<T> list = new List<T>();  
        foreach (DataRow row in tblRes.Rows)  
        {  
             T item = mssql.GetItem<T>(row);  
             list.Add(item);  
         }  
    
         return await Task.FromResult(Results.Ok(list));  
    }  
    
    1. Here the problem is that the List need data type and generic method GetItem expects also a data type (class) < T >.
      1. originally I tried to determine the class via Activator, but this triggers compiler error CS0118 because of T
    public static async Task<IResult> GetMyData(string className)  
      {  
        Type temp = Type.GetType(className);  
        var T = Activator.CreateInstance(temp);  
    
        DataTable tblRes = new DataTable("tbl");  
        tblRes = await mssql.GetData("SELECT ....");  
    
        List<T> list = new List<T>();  
        foreach (DataRow row in tblRes.Rows)  
        {  
             T item = mssql.GetItem<T>(row);  
             list.Add(item);  
         }  
    
         return await Task.FromResult(Results.Ok(list));  
    }  
    

    I have a workaround and it looks like this:

    1. I don't use a generic method
      public static async Task<IResult> GetMyData(string className)  
      {  
    .  
    .  
             object item = mssql.GetItem(row, className);  
             list.Add(item);  
    .  
    .  
         return await Task.FromResult(Results.Ok(list));  
    }  
    
    1. I pass the class name to the method . Return value is then an object (because I don't have a T).
    2. in the method I can do what you suggest:
    public object GetItem(DataRow dr, string classname)  
    {  
        Type temp = Type.GetType(classname);  
        var obj = Activator.CreateInstance(temp);  
       .  
       .  
        return obj;  
    }  
    

    The workaround works, but I would have preferred to solve it via T because it seems cleaner to me.

    Does anyone have any ideas?

    Thanks

    EDIT

    The background is that I have a very large number of models (classes). So I can create an endpoint for each model in the WebApi, then I can also write out the class (e.g. if I want to fetch all products, I put the class "Products"):

        List<Products> list = new List<Products>();  
        foreach (DataRow row in tblRes.Rows)  
        {  
            Products item = mssql.GetItem<Products>(row);  
            list.Add(item);  
        }  
    

    Or I can do this generically, then I only need to program one endpoint in the WebApi, but can use it to address all classes by passing the class name from Client to WebApi and using generic methods in the WebApi. So for this to work, I need to get a real class from a class name (So I need to convert className to < T > ).


  3. Jack J Jun 25,296 Reputation points
    2023-01-10T08:17:40.18+00:00

    @d. simic , thanks for the feedback, you also could use reflection to use q as a class.

    Here is a code example you could refer to.

            static void Main(string[] args)
            {
                string nspace = &#34;ConsoleApp1.Model&#34;;
    
                var q = from t in Assembly.GetExecutingAssembly().GetTypes()
                        where t.IsClass &amp;&amp; t.Namespace == nspace
                        select t;
                foreach (var type in q)
                {
                    object instance = Activator.CreateInstance(type);
                    MethodInfo method = type.GetMethod(&#34;Say&#34;);
                    method.Invoke(instance, null);
                }
            }
    
    namespace ConsoleApp1.Model
    {
        public class Student
        {
            public void Say()
            {
                Console.WriteLine(&#34;I am a student&#34;);
            }
        }
        public class Teacher
        {
            public void Say()
            {
                Console.WriteLine(&#34;I am a teacher&#34;);
            }
        }
    }
    

    Result: 277738-image.png

    Best Regards, Jack


    If the answer is the right solution, please click "Accept Answer" and upvote it.If you have extra questions about this answer, please click "Comment".

    Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.

    0 comments No comments

  4. Alex Khassapov 0 Reputation points
    2023-08-23T06:34:01.4766667+00:00

    As I understand the question correctly, you want to use 'string' as generic 'T' for GetItem<T> and compiler complains that string can't be used as generic type. For that I create a wrapper class which on return implicitly converts to string:

    Then you can do:

    string s =  GetItem<str_wrap>();
    
            public class str_wrap
            {
                public str_wrap(string json)
                {
                    this.json = json;
                }
    
                public str_wrap()
                {
                    this.json = null;
                }
    
                public string json { get; set; }
    
                public static implicit operator string(str_wrap sw) => sw?.json;
            }
    
    0 comments No comments

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.