65.9K
CodeProject is changing. Read more.
Home

Redundant References Remover VSX

starIconstarIconstarIconstarIconstarIcon

5.00/5 (1 vote)

Jul 30, 2012

CPOL

2 min read

viewsIcon

8936

A Visual Studio Extension that locates and removes duplicate script references in a web project.

 


Introduction 

A Visual Studio Extension that locates and removes duplicate script references in a web project.

Problem Statement 

Whilst developing a web project, you will add some script references (JavaScript\CSS) to your page (well, duh).

But then you forget which scripts you added, and add them again.
(happens a lot with MVC 3 where you don't have a visual designer and the page can become quite big)

so basically you end up with something like this:



Referencing a script twice will not only slow down the website, it may also lead to an unexpected behavior as a JavaScript function calling a controller can be executed twice.

Sometimes the scenario gets more complicated, when a reference to the same script is included in both the layout/master page-file as well as a partial view.


Solution

Redundant References Remover Extension solves these problems, it scans the views in the web-project looking for duplicated references and removes them.

Downloads

Download Extension
Source Code

After the installation, the tool appears under Other Windows

Code Anatomy 

There are 3 services that do the work

IVisualStudioService handles Visual Studio Interaction
 public interface IVisualStudioService
    {
        WebProject GetWebProject();
 
        IList<Page> GetPages(ProjectItems item);
 
        vsSaveStatus RemoveReference(ProjectItem projectItem, string reference, int offset);
    } 
IPagesService handles processing of pages and scanning for repeated references
public interface IPagesService
    {
        void ProcessPages(IEnumerable<Page> pagesList);
 
        IEnumerable<Page> GetDuplicatesInSameFile(IEnumerable<Page> pages);
 
        IEnumerable<ParentChildDuplicates> GetDuplicatesInLayoutAndFile(IEnumerable<Page> pages);
    }  
IRegexService Extracts different parts of info from the Page's html
  public interface IRegexService
    {
        IEnumerable<string> GetReferences(string pageContent);
 
        string GetDefaultLayoutPageName(string content);
 
        IEnumerable<string> GetMultipleLayouts(string content);
 
        IEnumerable<string> GetPartialViews(string content);
 
        IEnumerable<string> GetWebControls(string content);
 
        bool CheckPageHasNullLayout(string content);
 
        string GetMasterPageName(string content);
    }  
There are 3 types of web projects, mvc2, mvc3 and web forms, each handle the master\child layout differently.

So we make the parent class WebFormPagesService with virtual functions.
namespace Services.Implementation
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using Entities;
    using Services.Contracts;
    using EnvDTE;
 
    public class WebFormPagesService : IPagesService
    {
        #region Member Variables
 
        protected readonly IRegexService _regexService;
 
        #endregion Member Variables
 
        #region Constructor
 
        public WebFormPagesService(IRegexService regexService)
        {
            this._regexService = regexService;
        }
 
        #endregion Constructor
 
        #region IPagesService
 
        void IPagesService.ProcessPages(IEnumerable<Page> pagesList)
        {
            foreach (Page page in pagesList)
            {
                page.References = GetReferences(page);
                var parents = GetParents(page.Name, page.Content, pagesList);
                if (page.Parents != null)
                {
                    page.Parents.Concat(parents);
                }
                else
                {
                    page.Parents = parents;
                }
                var children = GetChildren(page.Content, pagesList);
                foreach (var child in children)
                {
                    if (child.Parents == null)
                    {
                        child.Parents = new List<Page>();
                    }
 
                    child.Parents.Add(page);
                }
            }
 
            LinkParents(pagesList);
        }
 
        IEnumerable<Page> IPagesService.GetDuplicatesInSameFile(IEnumerable<Page> pages)
        {
            foreach (var page in pages)
            {
                var duplicates = page.References.Where(p => p.Value != 1).ToDictionary(p => p.Key, p => p.Value);
 
                if (duplicates.Count != 0)
                {
                    yield return new Page
                    {
                        Content = page.Content,
                        Item = page.Item,
                        Name = page.Name,
                        Parents = page.Parents,
                        References = duplicates
                    };
                }
            }
        }
 
        IEnumerable<ParentChildDuplicates> IPagesService.GetDuplicatesInLayoutAndFile(IEnumerable<Page> pages)
        {
            var duplicatesList = new List<ParentChildDuplicates>();
 
            foreach (var child in pages)
            {
                var childReferences = child.References.Keys;
 
                foreach (var parent in child.Parents)
                {
                    var parentsReferences = parent.References.Keys;
                    var commonReferences = parentsReferences.Intersect(childReferences);
 
                    if (commonReferences.Count() > 0)
                    {
                        var parentChildDuplicate = new ParentChildDuplicates { Parent = parent, Child = child, Duplicates = commonReferences };
                        duplicatesList.Add(parentChildDuplicate);
                    }
                }
            }
 
            return duplicatesList;
        }
 
        #endregion
 
        .
 
        .
 
        .
    }
}
  

Conclusion

That's it, the code is pretty much straight forward.
If you have any comment\suggestion\question about the implementation\code topology drop me a line ;)

Improvement\Future Work

Currently the code doesn't support detecting the Inclusion of both a minified and unminified version of a script.

... src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.js">
... src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js">  
But I'll get to it, when I have time.

Useful Resources

YSlow rule #13 – Remove Duplicate Scripts
Introduction to Visual Studio 2010 Extensibility
Manipulating Project Files in VS
Customizing Visual Studio Extension Icon in Visual Studio 2010