Sharing is Caring: Using Shared Projects in ASP.NET

As software developers, copy-pasting some code or a few files and having it in multiple places has always been smelly. There are several different avenues that you can take to avoid this kind of redundancy, but this post will focus on one that it seems isn't talked about too often: Shared Projects.

Avoiding Redundancy through Linking

Prior to the introduction to Shared Projects, I'll briefly go over why Shared Projects themselves were introduced, and how you might have accomplished similar behavior before their release.

With the major Visual Studio initiative over the past few years to go cross-platform, an issue arose with how to go about organizing the code that would be accessed across multiple platforms without simply going copy-paste crazy. The answer to this was the fabled "Add as Link" option:

Adding a file as a link

You could simply click on a project, choose the Add Existing File option and then select the "Add as Link" option that appeared in the dialog box. This would essentially create a pointer to the original file that would appear within both projects.

The problem with this - it was tedious. You could only select a single file at a time, and imagine if you had to reference multiple files across multiple platforms? It's enough to make your wrist tingle at the thought.

There simply had to be a better way, and the proposed solution: shared projects.

What is a Shared Project?

The concept of shared projects were introduced in Visual Studio 2013 RC 2, and in a nutshell, allow you to reference an entire project as opposed to just a single assembly like you would with a class library.

Now how does this work? There are all types of things contained within a project that I might not want to use? How do I handle references within the Shared Project? What about all that stuff?

It's important to note that the term "Shared Project" might as well be "Shared Bucket of Code" as it functions more as a placeholder project that you can store all of your code and other files such as resources in. At compile time, any projects that reference this Shared Project will have all of the files (including folder structure, etc.) incorporated into them and then they will be compiled, or in other words:

A Shared Project is not going to be compiled on its own, but rather the code within the project will be incorporated into each assembly that references it and compiled within them.

Now that you have a fairly decent idea of what a Shared Project is, let's see how you might go about using it within your application.

Using a Shared Project

Shared Projects are just as easy to create as any other file or folder, and generally just require a few clicks to get up and running.

To add a Shared Project to your existing solution, right-click on the Solution and choose "Add Project", and then select "Shared Project" from the Visual C# tab:

You'll see the Shared Project appear within the Solution Explorer and from this point, you can begin adding any types of files that you want to use to it. In this example, we will create a class that will expose a function that we will want to be accessible in two other projects.

For this example, we will create a new file that makes an attempt to add, but just not very well:

namespace Common
{
    public static class Math
    {
        /// 
        /// An unreliable addition method
        ///
        public static int DefunctAdd(int x, int y)
        {
            return (x + y) + 3;
        }
    }
}

Next we will want to add a reference to this within another project. This works similar to how you might be accustomed to adding references in the past. Right-click the project you want to use the Shared Project in, choose Add Reference, and select the Shared Project under the "Shared Projects" tab that appears on the left:

After adding this as a reference, you could now simply use the following to access this method within your targeted projects:

var result = Common.Math.DefunctAdd(3, 7);

Nothing too special here. This is something you've always been able to do. Well, let's spice it up a bit by applying conditional compilation attributes to have the function behave differently based on the targeted platform:

public static int DefunctAdd(int x, int y)
{
#if NETCOREAPP1_1
            return (x + y) + 3;
#else
            return (x + y) + 13;
#endif
}

Since this file will be compiled independently by each project that references it, we can reference the Shared Project in two separate applications and yield the following results:

// .NET Core 1.1
Common.DefunctAdd(3, 7); // returns 13
// .NET Core 1.0
Common.DefunctAdd(3, 7); // returns 23

This is a fairly simple example, but the use-cases expand a great deal if you are building a cross-platform application or need to share resources (i.e. images, CSS, XAML, etc.) across them.

Now you might be asking yourself at this point, why would I do all of this? Couldn't I just use a portable class library to accomplish this? Yes, you absolutely could, but would it be the best way to tackle your problem?

As usual in the world of software, it depends, and in the next section we will compare these two approaches and see how they differ.

Shared Projects vs Portable Class Libraries

The key differences come down to how these entities are compiled, and how they are reused:

  • Portable Class libraries are compiled as you have come to know and expect, and the unit of reuse is the assembly it (i.e. you reference YourLibrary.dll within your other projects).

  • Shared Projects on their own are not compiled, but rather the code contained in the project is incorporated into each assembly that references the shared project prior to compilation. Therefore, the unit of reuse within a shared project is actually the source code itself (i.e. you are referencing a "container" of code that you want to include within another project).

Now a common use-case for a Shared Project would be a scenario where you had to build an application to target multiple platforms. Normally, you might consider building a Portable Class Library (PCL), but let's see how that might compare to a Shared Project:

Shared Projects Portable Class Libraries
Code Reuse Level Source Code (i.e. you reference a project containing all the source code you want to use) Assembly (i.e. you reference a YourLibrary.dll file)
What happens at compile-time? All of the source code within the Shared Project is copied into each referenced project and compiled within them Nothing new here, it's compiled as expected
Visual Studio Support Since Visual Studio 2013 Update 2 Since the beginning of time
#IFDEF Support Full Support Unsupported (as each platform is compiled separately, must be accomplished through IoC)
.NET Framework Support Full Support Limited (functionality will be based on the subset of platforms targeted)

Why would I want to use one other the other?

Both Shared Projects and Portable Class Libraries are commonly encountered when building cross-platform applications and rightfully so.

The idea of sharing code across multiple projects and potentially different platforms and architectures is incredibly appealing. When you consider extending this to not only over code, but resources, XAML, even Javascript, CSS, and other client-side files, it really demonstrates the power of these concepts.

While both of these mechanisms can be used to avoid redundancy, preference is going to come down to the the specific reuse level that your project needs (and when you need the content to be re-used).

Since a Shared Project covers most scenarios that would usually involve a Portable Class Library, here are a few things to consider before using one:

  • Is the assembly important in your scenario? If it's all that you need to share, then a PCL might be just fine.
  • Do you need to share things other than some C# code such as Javascript or other resources? A Shared Project would be ideal for this.
  • Do you need multiple applications to compile the same code in different ways (either through different referenced assemblies or via directives)? You'll really only be able to accomplish this via a Shared Project.
  • Do you know exactly what version of the framework you will be targeting? If so, then a Portable Class Library could be used.
  • Need code partitioning and to simply not shared everything? With a Shared Project, it's all or nothing (at least without quite a bit of tinkering to ignore things), so a Portable Class Library will allow you to only share what you need.
  • Need to unit test everything? Portable Class Libraries are going to lend themselves to unit testing better as Shared Projects will compile a given file in multiple projects (which could lead to different results).

So - as with most things when building applications, it depends.

Closing Thoughts on Shared Projects

Tabs or spaces? React or Angular? Shared Projects or Portable Class Libraries? All of these are common discussions when building applications or making decisions related to them, and everyone has their own opinions on which is correct.

Personally, I'm a big fan of Shared Projects and in my opinion they are more versatile than PCLs and allow you to quickly add new platform specific features without a great deal of unnecessary complexity, when you can use them that is.

This does come at a cost however, especially when you aren't just sharing code and instead targeting multiple platforms. Conditional compilation directives can make your code difficult to test, which in turn, can introduce errors that you won't know about until you've actually compiled your application.

So if you just need to share some core classes across your business application, or need to share functionality across multiple platforms, you might try giving Shared Projects a spin. As stated multiple times throughout this post, they aren't always for everyone or every scenario, but when they are appropriate I've found that they can make your live much easier.