With the culture of knowledge sharing and open source spreading, everyone races to show they have something valuable that you may want. And while you may not ask for money for your content you may still want to get something in return, say a contact, an email address that’s verified (or not), to keep in touch with the consumer of your content.

Yet a full fledged registration doesn’t seem like a proper thing to do – cluttering your EPiServer user repository with (let’s face it – for a large part fake or temporary email addresses that user create only to get your content).

While there may be a lot of ways to handle that (streaming it through a page Response.WriteFile might seem as one of the more obvious ones), I would like to show you a cleaner, simpler and more elegant way that I’ve come up with.

We really don’t want people to deep link to our files without them knowing the files are from our site, that’s just rude – so hiding them behind an obscure URL wouldn’t work (thus we cannot use the regular file providers). We’ve already establish that we don’t want to log them in, so setting file rights are useless. But I want all the benefits including client-side caching.

Basically the solution boils down to creating a thin layer over the File provider of our choice, in my case the versioning file provider. The only method we need to override in it is GetFile. I want to allow downloading for logged in users and I want to allow downloading for all users that have a “magic-cookie” set. If either of the conditions are met, the file just downloads using the underlying provider’s routines including all the logic EPiServer has put for caching and rights. But if neither of the requirements are met, the user is directed to a page of our choice.

public override VirtualFile GetFile(string virtualPath) { string handledPath; if (TryGetHandledAbsolutePath(virtualPath, out handledPath)) { if ((HttpContext.Current.Profile != null && !HttpContext.Current.Profile.IsAnonymous) || HttpContext.Current.Request.Cookies.AllKeys.Contains(PASS_COOKIE_NAME)) { return base.GetFile(virtualPath); } HttpContext.Current.Response.Redirect( string.Format(registrationFormUrl,HttpContext.Current.Server.HtmlEncode(virtualPath))); return null; } return Previous.GetFile(virtualPath); }

How you specify that page’s totally up to you, but there is a nice initialization routine called during the Virtual Path Provider loading phase where all of the settings that are specified in your relevant web.config VPP declaration are passed to you, why not use it? What you’ll need define your VPP in the web.config is what follows:

<add showInFileManager="true" virtualName="CookieEnabled" virtualPath="~/CookieEnabled/" bypassAccessCheck="false" name="CookieEnabled" type="Cognifide.EPiServer.CookieEnabledVirtualPathProvider.CookieEnabledVirtualPathProvider,Cognifide.EPiServer.CookieEnabledVirtualPathProvider" indexingServiceCatalog="Web" physicalPath="C:\vpp\Resources" RegistrationFormUrl="/File-Asset-Request-Form/?filepath={0}" />


you can then define your class as:

public class CookieEnabledVirtualPathProvider : VirtualPathVersioningProvider { private const string REGISTRATION_FORM_URL_WEB_CONFIG_PARAM_NAME = "RegistrationFormUrl"; public const string PASS_COOKIE_NAME = "ResourcePass"; private string registrationFormUrl; public CookieEnabledVirtualPathProvider(string name, NameValueCollection configParameters) : base(name, configParameters) { registrationFormUrl = configParameters[REGISTRATION_FORM_URL_WEB_CONFIG_PARAM_NAME]; } ... }

So we know now where we want to direct people without a “magic-cookie” who want to get our assets, and how to do it, but how do we finally allow that file to get down to them?

In your page – for the same of simplicity I assume you will be using XForms for gathering the user data, but that really does not matter. When you validate the form data (or get the user to click on a link that you’ve sent them) you just set the “magic” cookie that I called “ResourcePass” to any value and to to expire in some a long amount of time, like a year. So that they can now access your files unrestrained and direct them BACK at the URL they came from – and now the VPP will allow them to just download the file they initially requested:

protected void XForm_BeforeSubmitPostedData(object sender, SaveFormDataEventArgs e) { if (!Page.IsValid) { e.CancelSubmit = true; } else { string requestedFile = Request.Params["filepath"]; HttpCookie cookie = new HttpCookie(CookieEnabledVirtualPathProvider.PASS_COOKIE_NAME, "true"); cookie.Expires = DateTime.Now.AddYears(1); cookie.HttpOnly = true; Response.Cookies.Add(cookie); if (!string.IsNullOrEmpty(requestedFile)) { Response.Redirect(requestedFile); } } }
1 Star2 Stars3 Stars4 Stars5 Stars (1 votes, average: 4.00 out of 5)
Loading...Loading...



This entry (Permalink) was posted on Thursday, March 12th, 2009 at 10:01 pm and is filed under .Net Framework, ASP.NET, C#, EPiCode, EPiServer, Open Source, Software, Software Development, Solution, Web applications. You can follow any responses to this entry through the RSS 2.0 feed. You can leave a response , or trackback from your own site.



  • Øyvind

    Great post, easy to read and easy to understand. I had this functionality for EPiServer 4, but had not converted the project to to EPiServer CMS5 yet.

  • http://www.najmanowicz.com/ Adam Najmanowicz

    Thanks Øyvind! Hope you’ll find a use for that again :)

  • Øyvind

    A couple of things a came across when testing this provider. This provider gives the form one time for the complete directory and not for each file in the folder (by design, but nice to know).

    There are also issues when adding a file to the directory. The public override VirtualFile GetFile(string virtualPath) is executed also when adding files. So the form registration should not be redirected when in editmode. The same method will of course be exceuted when drag n drop of files are done. There are no methods to check this in EpiServer so after taking a look at the querystring this is my check for drag n drop; HttpContext.Current.Request[“islastbits”] and this is for adding files in the File Manager; HttpContext.Current.Request[“plugin”]. Could also check from inheritance from ActionWindow in the last one.

    Else, this is working like a charm :D

  • http://www.najmanowicz.com/ Adam Najmanowicz

    Thanks again Øyvind! You’re right about the one time asking – if you want to work around that you can simply delete the cookie when the file is being served, but in my case I wanted to only ask it once because once I have the information provided by the user I don’t want to nag them any longer. In my case I simply extract the email from the EPiServer XForm and use that to track any subsequent downloads.

    In my case the code for extracting the email looks as follows:

    
    string userEmail = e.FormData.GetValue("Email");
    HttpCookie cookie = new HttpCookie(CookieEnabledVirtualPathProvider.PASS_COOKIE_NAME, userEmail);
    

    So now in every request for a file I have the user’s email and then. when the user requests the file. I check

    1) is the user a registered Episerver user – if so i don’t need to use the cookie as I prefer to know it’s our own user. Otherwise I extract the email from the cookie:

    
    bool isLoggedIn = HttpContext.Current.Profile != null && 
        !HttpContext.Current.Profile.IsAnonymous;
    bool isRegistered = HttpContext.Current.Request.Cookies.AllKeys.Contains(PASS_COOKIE_NAME);
    string email = isRegistered 
        ? 
        HttpContext.Current.Request.Cookies[PASS_COOKIE_NAME].Value 
        : 
        (isLoggedIn ? HttpContext.Current.Profile.UserName : "unregistered");
    

    now once I have the email – it’s stored in the database (in my case a simple Entity Framework context)

    private void LogDownload (UnifiedFile file, string email)
    {
        dbCookieProviderEntities context = new dbCookieProviderEntities();
        FileDownloadEntry logEntry = new FileDownloadEntry();
        logEntry.FilePath = file.VirtualPath;
        logEntry.DownloadDate = DateTime.Now;
        logEntry.UserEmail = email;
        context.AddToFileDownloadLog(logEntry);
        context.SaveChanges();
    }
    

    Once I have that I can provide nice statistics to the site owners regarding who downloaded what files. Which files have been downloaded how often and when.

    If you’re interested, I can upload the full solution somewhere for you.