{"id":3508,"date":"2012-03-08T15:19:00","date_gmt":"2012-03-08T15:19:00","guid":{"rendered":"https:\/\/test.simple-talk.com\/uncategorized\/why-you-shouldnt-add-methods-to-interfaces-in-apis\/"},"modified":"2016-07-28T10:50:44","modified_gmt":"2016-07-28T10:50:44","slug":"why-you-shouldnt-add-methods-to-interfaces-in-apis","status":"publish","type":"post","link":"https:\/\/www.red-gate.com\/simple-talk\/blogs\/why-you-shouldnt-add-methods-to-interfaces-in-apis\/","title":{"rendered":"Why you shouldn&#8217;t add methods to interfaces in APIs"},"content":{"rendered":"<p>It is an oft-repeated maxim that you shouldn&#8217;t add methods to a publically-released interface in an API. Recently, I was hit hard when this wasn&#8217;t followed.<\/p>\n<p>As part of the work on <a href=\"http:\/\/www.applicationmetrics.com\/\">ApplicationMetrics<\/a>, I&#8217;ve been implementing auto-reporting of MVC action methods; whenever an action was called on a controller, ApplicationMetrics would automatically report it without the developer needing to add manual <code>ReportEvent<\/code> calls. Fortunately, MVC provides easy hook when a controller is created, letting me log when it happens &#8211; the <code>IControllerFactory<\/code> interface.<\/p>\n<p>Now, the dll we provide to instrument an MVC webapp has to be compiled against .NET 3.5 and MVC 1, as the lowest common denominator. This MVC 1 dll will still work when used in an MVC 2, 3 or 4 webapp because all MVC 2+ webapps have a <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/eftw1fys.aspx\">binding redirect<\/a> redirecting all references to previous versions of System.Web.Mvc to the correct version, and <a href=\"https:\/\/www.simple-talk.com\/community\/blogs\/simonc\/archive\/2011\/12\/23\/104984.aspx\">type forwards<\/a> taking care of any moved types in the new assemblies. Or at least, it should.<\/p>\n<h4>IControllerFactory<\/h4>\n<p>In MVC 1 and 2, <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/system.web.mvc.icontrollerfactory.aspx\"><code>IControllerFactory<\/code><\/a> was defined as follows:  <\/p>\n<pre>public interface IControllerFactory\n{\n    IController CreateController(RequestContext requestContext, string controllerName);\n    \n    void ReleaseController(IController controller);\n}<\/pre>\n<p>  So, to implement the logging controller factory, we simply wrap the existing controller factory:  <\/p>\n<pre>internal sealed class LoggingControllerFactory : IControllerFactory\n{\n    private readonly IControllerFactory m_CurrentController;\n    \n    public LoggingControllerFactory(IControllerFactory currentController)\n    {\n        m_CurrentController = currentController;\n    }\n    \n    public IController CreateController(\n        RequestContext requestContext, string controllerName)\n    {\n        \/\/ log the controller being used\n        FeatureSessionData.ReportEvent(\"Controller used:\", controllerName);\n        \n        return m_CurrentController.CreateController(requestContext, controllerName);\n    }\n    \n    public void ReleaseController(IController controller)\n    {\n        m_CurrentController.ReleaseController(controller);\n    }\n}<\/pre>\n<p>Easy. This works as expected in MVC 1 and 2. However, in MVC 3 this type was throwing a <code>TypeLoadException<\/code>, saying a method wasn&#8217;t implemented. It turns out that, in MVC 3, the definition of <code>IControllerFactory<\/code> was changed to <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/system.web.mvc.icontrollerfactory%28v=vs.98%29.aspx\">this<\/a>:<\/p>\n<pre>public interface IControllerFactory\n{\n    IController CreateController(RequestContext requestContext, string controllerName);\n    \n    SessionStateBehavior GetControllerSessionBehavior(\n        RequestContext requestContext, string controllerName);\n        \n    void ReleaseController(IController controller);\n}<\/pre>\n<p>There&#8217;s a new method in the interface. So when our MVC 1 dll was redirected to reference System.Web.Mvc v3, <code>LoggingControllerFactory<\/code> tried to implement version 3 of <code>IControllerFactory<\/code>, was missing the <code>GetControllerSessionBehaviour<\/code> method, and so couldn&#8217;t be loaded by the CLR.<\/p>\n<h4>Implementing the new method<\/h4>\n<p>Fortunately, there was a workaround. Because interface methods are normally implemented implicitly in the CLR, if we simply declare a virtual method matching the signature of the new method in MVC 3, then it will be ignored in MVC 1 and 2 and implement the extra method in MVC 3:  <\/p>\n<pre>internal sealed class LoggingControllerFactory : IControllerFactory\n{\n    ...\n    \n    public virtual SessionStateBehaviour GetControllerSessionBehaviour(\n        RequestContext requestContext, string controllerName) {}\n    \n    ...\n}<\/pre>\n<p>However, this also has problems &#8211; the <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/system.web.sessionstate.sessionstatebehavior.aspx\"><code>SessionStateBehaviour<\/code><\/a> type only exists in .NET 4, and we&#8217;re limited to .NET 3.5 by support for MVC 1 and 2.<\/p>\n<p>This means that the <em>only<\/em> solutions to support all MVC versions are:<\/p>\n<ol>\n<li>Construct the <code>LoggingControllerFactory<\/code> type at runtime using reflection<\/li>\n<li>Produce entirely separate dlls for MVC 1&amp;2 and MVC 3.<\/li>\n<\/ol>\n<p>Ugh. And all because of that blasted extra method!<\/p>\n<h4>Another solution?<\/h4>\n<p>Fortunately, in this case, there is a third option &#8211; System.Web.Mvc also provides a <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/system.web.mvc.defaultcontrollerfactory.aspx\"><code>DefaultControllerFactory<\/code><\/a> type that can provide the implementation of <code>GetControllerSessionBehaviour<\/code> for us in MVC 3, while still allowing us to override <code>CreateController<\/code> and <code>ReleaseController<\/code>.<\/p>\n<p>However, this does mean that <code>LoggingControllerFactory<\/code> won&#8217;t be able to wrap any calls to <code>GetControllerSessionBehaviour<\/code>. This is an acceptable bug, given the other options, as very few developers will be overriding <code>GetControllerSessionBehaviour<\/code> in their own custom controller factory.<\/p>\n<p>So, if you&#8217;re providing an interface as part of an API, then please please <em>please<\/em> don&#8217;t add methods to it. Especially if you don&#8217;t provide a &#8216;default&#8217; implementing type. Any code compiled against the previous version that can&#8217;t be updated will have some very tough decisions to make to support both versions.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>It is an oft-repeated maxim that you shouldn&#8217;t add methods to a publically-released interface in an API. Recently, I was hit hard when this wasn&#8217;t followed. As part of the work on ApplicationMetrics, I&#8217;ve been implementing auto-reporting of MVC action methods; whenever an action was called on a controller, ApplicationMetrics would automatically report it without&#8230;&hellip;<\/p>\n","protected":false},"author":186659,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[2],"tags":[],"coauthors":[],"class_list":["post-3508","post","type-post","status-publish","format-standard","hentry","category-blogs"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/3508","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/users\/186659"}],"replies":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/comments?post=3508"}],"version-history":[{"count":1,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/3508\/revisions"}],"predecessor-version":[{"id":25470,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/3508\/revisions\/25470"}],"wp:attachment":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media?parent=3508"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/categories?post=3508"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/tags?post=3508"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/coauthors?post=3508"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}