31 December 2015 WebAPI, ASP.NET Robert Muehsig

Embedded files? Why?

In a normal Web-Application all files are somehow stored as files in the app directory, but sometimes it could be handy to embed those files.

One scenario could be that you have a “library”, which can be integrated in a larger application. If you don’t want to pull over all files and you just want to expose a single assembly (for example as NuGet package) embedded resources might come handy.

Demo-Application

My demo application is a simple ConsoleApp, which a selfhosting WebAPI and two Controllers (Demo and Pages):

x

Important is, that my “target” html and css file are marked as Embedded Resource.

Routing

In my sample I have created on “PageController”, which accepts all requests that seems to target the embedded files.

Registration:

public class Startup
{
    public void Configuration(IAppBuilder appBuilder)
    {
        HttpConfiguration config = new HttpConfiguration();

        config.MapHttpAttributeRoutes();

        config.Routes.MapHttpRoute(
            name: "ApiV1",
            routeTemplate: "api/v1/{controller}/{id}",
            defaults: new { id = RouteParameter.Optional }
            );

        config.Routes.MapHttpRoute(
           name: "PageController",
           routeTemplate: "{*anything}",
           defaults: new { controller = "Page", uri = RouteParameter.Optional });

        appBuilder.UseWebApi(config);
    }

}

The “PageController”

This controller will try to read the HTTP GET PathAndQuery and will look inside the assembly resources for something with the same name.

public class PageController : ApiController
{
    private const string ResourcePath = "SelfHostWithBetterRouting.Pages{0}";

    public static string GetStreamContent(string folderAndFileInProjectPath)
    {
        var asm = Assembly.GetExecutingAssembly();
        var resource = string.Format(ResourcePath, folderAndFileInProjectPath);

        using (var stream = asm.GetManifestResourceStream(resource))
        {
            if (stream != null)
            {
                var reader = new StreamReader(stream);
                return reader.ReadToEnd();
            }
        }
        return String.Empty;
    }


    public HttpResponseMessage Get()
    {
        var virtualPathRoot = this.Request.GetRequestContext().VirtualPathRoot;
        string filename = this.Request.RequestUri.PathAndQuery;

        // remove SERVER/appname from request to get the relative filename
        if (virtualPathRoot != "/")
        {
            filename = filename.ToLowerInvariant().Replace(virtualPathRoot.ToLowerInvariant(), string.Empty);
        }
        
        // input as /page-assets/js/scripts.js
        if (filename == "/" || filename == "")
        {
            filename = ".index.html";
        }

        // folders will be seen as "namespaces" - so replace / with the .
        filename = filename.Replace("/", ".");
        // resources can't be named with -, so it will be replaced with a _
        filename = filename.Replace("-", "_");

        var mimeType = System.Web.MimeMapping.GetMimeMapping(filename);

        var fileStreamContent = GetStreamContent(filename);

        if (string.IsNullOrWhiteSpace(fileStreamContent))
        {
            throw new Exception(string.Format("Can't find embedded file for '{0}'", filename));
        }

        if (virtualPathRoot != "/")
        {
            fileStreamContent = fileStreamContent.Replace("~/", virtualPathRoot + "/");
        }
        else
        {
            fileStreamContent = fileStreamContent.Replace("~/", virtualPathRoot);
        }

        var response = new HttpResponseMessage();
        response.Content = new StringContent(fileStreamContent);
        response.Content.Headers.ContentType = new MediaTypeHeaderValue(mimeType);
        return response;
    }

}	

Mix the “PageController” and normal WebAPI Controllers

In my sample the “PageController” will catch all requests that are not handled by other controllers, so you could even serve a general 404 page.

Hosting in IIS

If you host this inside an IIS it will not work out of the box, because the IIS itself tries to serve static content. One easy option would be to include this inside your web.config:

<!-- prevent IIS from serving embeddded stuff -->
<location path="pages">
    <system.webServer>
        <handlers>
            <add name="nostaticfile" path="*" verb="GET" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />
        </handlers>
    </system.webServer>
</location>

With this web.config setting in place the request should route through your code.

Result

The self hosting WebAPI returns the “index.html” and the linked “site.css” - all embedded inside the assembly:

x

In an older blogpost I used a similar approach, but the routing part is now “better” solved.

Hope this helps!

The code is also available on GitHub.


Written by Robert Muehsig

Software Developer - from Saxony, Germany - working on primedocs.io. Microsoft MVP & Web Geek.
Other Projects: KnowYourStack.com | ExpensiveMeeting | EinKofferVollerReisen.de