MVC Razor, Include JS / CSS files from another project

14,871

Solution 1

I do this very thing. However I embed the Javascript files and other content in another DLL and then call them from my razor syntax like so. Here is the code I use. In the View: Script example:

        <script [email protected]("GetEmbeddedResource", "Shared", new { resourceName = "Namespace.Scripts.jquery.qtip.min.js", pluginAssemblyName = @Url.Content("~/bin/Namespace.dll") }) type="text/javascript" ></script>

Image Example:

@Html.EmbeddedImage("corporate.gif", new { width = 150, height = 50})

Here is my helper methods:

        public static MvcHtmlString EmbeddedImage(this HtmlHelper htmlHelper, string imageName, dynamic htmlAttributes)
    {
        UrlHelper url = new UrlHelper(HttpContext.Current.Request.RequestContext);
        var anchor = new TagBuilder("img");
        anchor.Attributes["src"] = url.Action("GetEmbeddedResource", "Shared",
                                              new
                                                  {
                                                      resourceName = "Namespace.Content.Images." + imageName,
                                                      pluginAssemblyName = url.Content("~/bin/Namespace.dll")
                                                  });

        if (htmlAttributes != null)
        {
            string width = "";
            string height = "";
            PropertyInfo pi = htmlAttributes.GetType().GetProperty("width");
            if (pi != null)
                width = pi.GetValue(htmlAttributes, null).ToString();

            pi = htmlAttributes.GetType().GetProperty("height");
            if (pi != null)
                height = pi.GetValue(htmlAttributes, null).ToString();

            if (!string.IsNullOrEmpty(height))
                anchor.Attributes["height"] = height;

            if (!string.IsNullOrEmpty(width))
                anchor.Attributes["width"] = width;
        }
        return MvcHtmlString.Create(anchor.ToString());
    }

Lastly my shared Controller:

        [HttpGet]
    public FileStreamResult GetEmbeddedResource(string pluginAssemblyName, string resourceName)
    {
        try
        {
            string physicalPath = Server.MapPath(pluginAssemblyName);
            Stream stream = ResourceHelper.GetEmbeddedResource(physicalPath, resourceName);
            return new FileStreamResult(stream, GetMediaType(resourceName));
            //return new FileStreamResult(stream, GetMediaType(tempResourceName));
        }
        catch (Exception)
        {
            return new FileStreamResult(new MemoryStream(), GetMediaType(resourceName));
        }
    }

    private string GetMediaType(string fileId)
    {
        if (fileId.EndsWith(".js"))
        {
            return "text/javascript";
        }
        else if (fileId.EndsWith(".css"))
        {
            return "text/css";
        }
        else if (fileId.EndsWith(".jpg"))
        {
            return "image/jpeg";
        }
        else if (fileId.EndsWith(".gif"))
        {
            return "image/gif";
        }
        else if (fileId.EndsWith(".png"))
        {
            return "image/png";
        }
        return "text";
    }

Resource Helper:

    public static class ResourceHelper
{
    public static Stream GetEmbeddedResource(string physicalPath, string resourceName)
    {
        try
        {
            Assembly assembly = PluginHelper.LoadPluginByPathName<Assembly>(physicalPath);

            if (assembly != null)
            {
                string tempResourceName = assembly.GetManifestResourceNames().ToList().FirstOrDefault(f => f.EndsWith(resourceName));
                if (tempResourceName == null)
                    return null;
                return assembly.GetManifestResourceStream(tempResourceName);
            }
        }
        catch (Exception)
        {

        }

        return null;
    }
}  

Plugin Helper

public static T LoadPluginByPathName<T>(string pathName)
{
    string viewType = typeof(T).GUID.ToString();

    if (HttpRuntime.Cache[viewType] != null)
        return HttpRuntime.Cache[viewType] is T ? (T)HttpRuntime.Cache[viewType] : default(T);

    object plugin = Assembly.LoadFrom(pathName);
    if (plugin != null)
    {
        //Cache this object as we want to only load this assembly into memory once.
        HttpRuntime.Cache.Insert(viewType, plugin);
        return (T)plugin;
    }

    return default(T);
}

Remember that I am using these as embedded content!

Solution 2

To my knowledge you can't do this as the path would lie outside of the website.

You can however do the following:

1) Put all the scripts you want to share in Common.Web\Scripts
2) For each script file in your Web application 'Add as Link' to your Common.Web Scripts (you don't even need to do this step; it is however nice to see what scripts your web app uses in VS)
3) Add a post-build event to your Web application that copies the scripts from Common.Web\Scripts to your WebApp\Scripts folder:

copy $(ProjectDir)..\Common.Web\Scripts* $(ProjectDir)\Scripts

so from your perspective in Visual Studio you will only have a single place to update your .js files that can be used by multiple projects.

Solution 3

Instead of using Url helper use relative addressing. What you tried to do makes no sense, as helper is used to resolve paths relative to it's project.

Since you are trying to use resources of another project, it's ok to assume that you know upfront where you're going to deploy each project. Even though I don't like this practice, I can think of a pragmatic solution for this.


If your two applications are at urls:

http://www.mysite.com/app1
http://www.mysite.com/Common.Web

you could address like this:

<script src="@Url.Content("~")/../Common.Web/Scripts/bootstrap-typeahead.js")" type="text/javascript"></script>

meaning, resolve my app root folder, go up a level, and go down rest of the path.

Share:
14,871

Related videos on Youtube

Martin
Author by

Martin

Front-end ninja and CSS lover. Started programming when I was 15. Passionate kitesurfer and ocean addict. Or if you want the long version... Skilled full stack developer with eight years professional experience in the full development lifecycle, from development, delivery and testing. Flexible team player with excellent communication skills that thrives in a fast-paced work environment. Passionate about applying strong problem-solving skills to create feature-rich platforms and interactive user interface. Driven by creating efficient and adaptable solutions, with great focus on quality assurance. B.Sc. in Informatics and Microsoft certified MCSD in Web Applications. —— Development experience includes —— • Vue • React • Angular • Node • Express • MongoDB • GraphQL • Responsive Web Design • SCSS • LESS • PostCSS • HTML5 • NPM • Babel • Webpack • ES6

Updated on September 15, 2022

Comments

  • Martin
    Martin over 1 year

    I've got a C# MVC project that uses Razor syntax.
    To be able to reuse some code, I want to put some of my JavaScript and CSS files in a different project and include them somehow.
    This is how my scripts are included at the moment:

    <script src="@Url.Content("~/Scripts/bootstrap-typeahead.js")" type="text/javascript"></script>
    <script src="@Url.Content("~/Scripts/bootstrap-dropdown.js")" type="text/javascript"></script>
    

    At the moment, the scripts are in the same project as the cshtml file but they should be placed in the Common.Web project instead...
    What I want to do is this (doesn't work though):

    <script src="@Url.Content("Common.Web/Scripts/bootstrap-typeahead.js")" type="text/javascript"></script>
    <script src="@Url.Content("Common.Web/Scripts/bootstrap-dropdown.js")" type="text/javascript"></script>
    
    • retslig
      retslig over 11 years
      If someone has answered your question please mark it has such.
  • Dmitry Efimenko
    Dmitry Efimenko over 11 years
    sounds like this could be a decent solution. However, you didn't provide code for PluginHelper used in ResourceHelper. I can't test it out without it. Also, in order to make sure the resources are going to be embedded, is all I need to do is mark them as "Embedded Resource" in build action in properties?
  • retslig
    retslig over 11 years
    I have edited the above for the plugin helper now. Correct all you need to to do is add mark as Embedded resource in the build action field under the properties of that file. Do this for all files.
  • wal
    wal over 10 years
    i havent used TFS but i would guess it would work because it would respect the post build event.
  • Bevan
    Bevan over 10 years
    This technique could cause some performance issues on a high traffic site. Personally, I wouldn't suggest using embedded resources. Orchard CMS has an interesting technique to include files from other projects so I would check that out!
  • Mazhar Qayyum
    Mazhar Qayyum about 7 years
    @Bevan A link pointing to related Orchard documentation will be much appreciated.
  • bafsar
    bafsar almost 7 years
    I didn't try example code for images but rest of it is perfect. :)
  • bafsar
    bafsar almost 7 years
    I have found an error. Last method, LoadPluginByPathName, has an logical error. When two assemblies with the same type but different path are sent, the system shows the same file handling and this is causing an exception. To solve this, I changed some codes: var viewType = pathName.Replace("~", "").Replace("/", "-"); if (HttpRuntime.Cache[viewType] != null && HttpRuntime.Cache[viewType] is T) return (T) HttpRuntime.Cache[viewType]; Now it works right.