I've found that there is way more repetition of code in a WebForms application than is immediately noticeable. I've been banging my head with this exact problem, and I've come up with several remedies.
DRY Up Your UserControls (And Models, Too!)
When you look at each individual page, you see so many differences that you can't possibly imagine code is being repeated. Look at the data you are saving to the database. On an application I work on, we have about 10 different forms all saving "people" records to the database with the same fields, but different fields are available in each view. At first I thought there was too much custom code, but then I realized it's all the same data in the same table. I created a generic User Control to edit "people" objects -- Populate form fields based on a domain model, and repopulate the domain model based on the form fields.
I also created a view-model that encompassed display logic, and then use that to affect the user control.
Person Domain Model
public enum PersonType
{
Applicant = 1,
Foo = 2
}
public class Person
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string TaxId { get; set; }
public IEnumerable<PersonType> Types { get; private set; }
public bool IsApplicant
{
get { return Types.Contains(PersonType.Applicant); }
}
public bool IsSomethingElse
{
get { return Types.Contains(PersonType.Foo); }
}
public bool RequiresTaxId
{
get { return IsApplicant; }
}
}
Person View-Model
public class PersonViewModel
{
public Person Person { get; private set; }
public bool ShowTaxIdField { get { return Person.RequiresTaxId; } }
public PersonViewModel(Person person)
{
Person = person;
}
}
(A Snippet Of) The User Control
<p id="TaxIdField" runat="server">
<asp:Label ID="TaxIdLabel" runat="server" AssociatedControlID="TaxId">Tax Id:</asp:Label>
<asp:TextBox ID="TaxId" runat="server" />
</p>
The C# Code-Behind
public partial class PersonUserControl : System.Web.UI.UserControl
{
public void SetModel(PersonViewModel model)
{
TaxId.Text = model.Person.TaxId;
TaxIdField.Visible = model.ShowTaxIdField;
// ...
}
public PersonViewModel GetModel()
{
var model = new PersonViewModel(new Person()
{
TaxId = TaxId.Text.Trim(),
// ...
});
return model;
}
}
Push Business Logic Into Your Domain Models
Removing duplicated code also means moving business logic into your domain models. Does this "person" require a tax Id? Well, the Person
class should have a property or method called IsTaxIdRequired
. Watch Crafting Wicked Domain Models for some additional ideas.
But I Use DataSets...
Stop! Immediately. You are intimately tying your C# code to the underlying database schema. Even if you have to hand write a mapping layer between DataSet objects and your domain models, you'll still be further ahead because now you can bundle behavior and business rules with your data in one neat, and tidy class. By using DataSets, your Design and Code-Behind files implement business logic! Stop this as soon as you can. Even though the WebForms framework doesn't strictly adhere to the Model-View-Controller pattern, the C# Code-Behind should be viewed as the "Controller" and the Design file should be seen as the "View". The only thing left is the "Model", which should not be a DataSet. DataSet objects give you data, but no behavior leading to repeated business logic.
Use Helper Classes For Initializing the User Interface
All to often I see this in 15 different UserControls:
public InitStateDropdownList()
{
ddlState.DataSource = ...
ddlState.DataBind();
ddlState.Items.Insert(0, new ListItem("Select", string.Empty));
}
Push this into a helper class:
public static class DropDownListHelper
{
public static void InitStates(DrowDownList list)
{
list.DataSource = // get options from database or session
list.Items.Insert(0, new ListItem("Select", string.Empty));
list.DataBind();
}
}
And use it:
DropDownListHelper.InitStates(ddlState);
Some people rile against "helper" classes, but it's surely better than writing what is essentially the same 3 lines of code in multiple places. Even if you can't create super generic User Controls, at least weed out the repetitive code that initializes components upon page load.
When is the BIG Rewrite the Answer?
Almost never. For all my complaints about the WebForms framework, it at least compartmentalizes pages and forms, which is a great setup for a long term refactoring job. The key is adding test coverage. I've had a lot of success using CodedUI Tests and SpecFlow for testing WebForms applications, since most if not all business logic is scattered between C# and ASP in 19 different files. At least write some tests to validate existing business rules don't get broken during the many refactoring sessions you have in your future.
It seems to me your question is split into two parts. The first revolves around duplication of interfaces. The second on dependencies.
First, duplication of interfaces. You probably want to duplicate them in the case you are describing.
A good rule of thumb when breaking up a monolith is to keep your boundaries clearly defined. If you have two domains, it can be counter-intuitive to keep in mind that any given domain object needs to be modeled in a way which is unique to the domain.
For instance, if you have a customer interface, ICustomer, and you have two domains, Orders and Invoices, although both orders and invoices utilize something called a customer, it is often better to define the interface twice than it is to try to force a single interface (usually due to a misunderstanding of DRY). This is because a customer from the perspective of the Order domain is a very different beast than a customer from the Invoices domain. It might seem intuitive that both Orders and Invoices share the same ICustomer interface. But in truth, they probably do not. A customer in an Order domain is very different than a customer in an Invoice domain, and will change for very different reasons.
So if you want to split your project into domain-driven micro services, create libraries around each domain. Don't be so much concerned about how well you can re-use code (or other resources) across domains, but how easy it is for the code in one domain to be changed without breaking code in an unrelated domain.
Second, dependencies. This will mostly solve itself after you address the above issue. Keeping dependencies simple is a matter of keeping dependencies secluded to their proper domain, and then pushing the interfaces for more generalized dependencies down into your base common libraries.
Best Answer
hmmm...
There are really two concepts at play here ...
There is Dynamic Library vs Static Library, which is what the question you refereed to was mostly dealing with. In this case what Neil here says and what the answer you referred to concluded is that on most implementations that use dynamic libraries the extra overhead is just not worth the trouble. Making libraries dynamically loadable by your application often times require some extra considerations regarding your code and how you deploy your application, that, unless you really need the feature, would often times make your app more complex rather than less.
Now, it is true that dynamic libraries are troublesome and should be used sparingly does not mean you should refrain from making libraries... quite the contrary, to split your code base in multiple quasi independent modules is just good practice, it will make your system overall easier to test and maintain.
Note that on some platform the concepts of dynamic and static libraries are quite dependent on the platform and language you are using. For example in Java, all libraries are on equal footings. I guess it would be somewhat in between dynamic and static where it is possible to change libraries without having to recompile what uses them but managing dynamically loadable and unloadable modules does require extra work to be done properly for anything more than the simplest use cases.
In most environments I have seen either it naturally supports dynamic libraries or if you wish to have dynamic libraries you need to specify this explicitly.
So in short YES do modularize your systems and do try to reduce interdependencies amongst your library, for example there should be no cycles when you create a graph on how your modules interconnect. (A->B->C->A) If such things occur then your separation is not quite right, if it just seems impossible to get rid of a cycle well perhaps thy were not meant to be separated in the first place, or perhaps your object modelization of your problem is deficient.
Note however that modularization does not eliminate the complexity completely, some of it is transferred to managing the interdependences, versions etc.
Hope this helps.