# Generic controllers in .NET Core

> Often controllers are really really similar to each other, here's a generic approach to this

2022-03-31 · Tim Cadenbach · https://www.tcdev.de/blog/generic-controllers-in-net-core/

---

<p>In many many repositories you can find tons of controllers, completely similar code with the only difference of serving different types.&nbsp;</p>
<p>Here's one approach to fix this. If you just want the full code, skip the article and look here -&gt; <a href="https://github.com/DeeJayTC/samples" title="https://github.com/DeeJayTC/samples" rel="follow">https://github.com/DeeJayTC/samples</a></p>
<h3>Using one generic controller for all the types in your project</h3>
<p>First step is to create a generic controller, this is a really simple part, just implement a controller as usually and add T to make it generic.&nbsp;<br />We also need to add a common interface to all the classes, I just named it IObjectBase. This is used to make sure all classes share the same ID Property.&nbsp;<br /><br />The Interface:</p>
<pre class="language-csharp"><code>   public interface IObjectBase&lt;TId&gt;
   {
      [Key]
      TId Id { get; set; }
   }</code></pre>
<p>The Controller:</p>
<pre class="language-csharp"><code>[Route("api/[controller]")]
[Produces("application/json")]
public class GenericController&lt;T&gt; : Controller where T : class,
   IObjectBase
{
   private readonly GenericDbContext db;

   public GenericController(GenericDbContext context)
   {
      this.db = context;
   }

   [HttpGet]
   public IQueryable&lt;T&gt; Get()
   {
      return this.db.Set&lt;T&gt;();
   }
....excluded for brevity, full sample in repo</code></pre>
<p></p>
<p>We also add a DBContext pretty much as usually, similar to this:<br /><br /></p>
<pre class="language-csharp"><code>public class GenericDbContext : DbContext
{
   public static IModel StaticModel { get; } = BuildStaticModel();

   public DbSet&lt;Something&gt; Somethings { get; set; }
   public DbSet&lt;SomeOtherThing&gt; OtherThing { get; set; }

   protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
   {
      if (!optionsBuilder.IsConfigured) optionsBuilder.UseInMemoryDatabase("ApplicationDb");
   }

   protected override void OnModelCreating(ModelBuilder builder)
   {
      base.OnModelCreating(builder);
   }

   private static IModel BuildStaticModel()
   {
      using var dbContext = new GenericDbContext();
      return dbContext.Model;
   }
}</code></pre>
<p></p>
<p>Don't forget to add the DBContext to your startup file!</p>
<h3>Lets put things together</h3>
<p>To tell .NET Core that we want to add additional controllers and routes we need to change the AddMVC call a bit</p>
<pre class="language-csharp"><code>builder.Services.AddMvc(o =&gt;
      o.Conventions.Add(new GenericControllerRouteConvention()))
       .ConfigureApplicationPartManager(m =&gt; m.FeatureProviders.Add(
          new GenericTypeControllerFeatureProvider(new[] {  Assembly.GetEntryAssembly().FullName}))
);</code></pre>
<p>The ApplicationPartManager and FeatureProvider allows you to add new controllers at runtime ( <a href="https://docs.microsoft.com/en-us/aspnet/core/mvc/advanced/app-parts?view=aspnetcore-6.0#:~:text=Feature%20providers%20work%20with%20application,common%20functionality%20between%20multiple%20apps." rel="follow noopener" target="_blank">See here</a> )</p>
<p>In the feature provider we need to find a way to find all the classes we want to use as a controller, here we are again using our Interface. This can be done using a custom attribute or anything similar thats shared by all classes supposed to create a controller.&nbsp;<br /><br />Here's a sample code for this:</p>
<pre class="language-csharp"><code>   public void PopulateFeature(IEnumerable&lt;ApplicationPart&gt; parts, ControllerFeature feature)
   {
      foreach (var assembly in this.Assemblies)
      {
         var loadedAssembly = Assembly.Load(assembly);
         var customClasses = loadedAssembly.GetExportedTypes()
            .Where(x =&gt; x.IsAssignableTo(typeof(IObjectBase)) &amp;&amp; x.Name != nameof(IObjectBase));

         foreach (var candidate in customClasses)
         {
            // Ignore BaseController itself
            if (candidate.FullName != null &amp;&amp; candidate.FullName.Contains("BaseController")) continue;

            // Generate type info for our runtime controller, assign class as T
            var propertyType = candidate.GetProperty("Id")
               ?.PropertyType;
            if (propertyType == null) continue;
            var typeInfo = typeof(GenericController&lt;,&gt;).MakeGenericType(candidate, propertyType)
               .GetTypeInfo();

            // Finally add the new controller via FeatureProvider -&gt;
            feature.Controllers.Add(typeInfo);
         }
      }
   }</code></pre>
<p><br />Last but not least we need to make AttributeRouting work, this can be done quite easily as well, there's a function called IControllerModelConvention.&nbsp;<br />We can use this to apply route conventions to all GenericController instances.&nbsp;</p>
<pre class="language-csharp"><code>   public void Apply(ControllerModel controller)
   {
      if (controller.ControllerType.IsGenericType)
      {
         var genericType = controller.ControllerType.GenericTypeArguments[0];
         controller.ControllerName = genericType.Name;
         controller.Selectors.Add(new SelectorModel
         {
            AttributeRouteModel = new AttributeRouteModel(new RouteAttribute($"/{genericType.Name}"))
         });
      }
   }</code></pre>
<p></p>
<h3>Final Words</h3>
<p>Implementing things like this allows you to have a shared controller for all types that don't need any specific work done. You can still add a normal controller for special cases and everything else, swagger for example, keeps working as usually.&nbsp;<br /><br />Just check the sample here -&gt;<a href="https://github.com/DeeJayTC/samples/tree/main/GenericControllers" rel="follow"> https://github.com/DeeJayTC/samples/tree/main/GenericControllers</a></p>
<p></p>
