Upload using Lightswitch & IIS 7: Server Not Found

Apr 25, 2012 at 6:02 PM

Dear Developers,

Uploading files works fine locally (with different settings in the web.Config of course), however after publishing my Lightswitch application on an IIS 7 server I get the following exception:

[HttpWebRequest_WebException_RemoteServer]

Arguments: NotFound

I copied the relevat sections from my web.config below:
(For local use I comment two lines at<system.webServer> and uncomment two lines at <system.web>)

 

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <appSettings>
    <!-- Upload Handlers -->
    <!-- see: http://interlink.codeplex.com/documentation -->
    <add key="UploadHandler" value="OhmrolleManagement.UploadHandler, Application.Server" />
    
    [...]
  </appSettings>
  <connectionStrings>
    [...]
  </connectionStrings>
  <system.web>
    <!-- LightSwitch trace.axd handler -->
    <trace enabled="true" localOnly="false" requestLimit="40" writeToDiagnosticsTrace="false" traceMode="SortByTime" mostRecent="true" />
    <httpHandlers>
      <!-- Upload HTTP Handlers -->
      <!-- only used for Debugging and IIS Classic Mode, disable for IIS-->
      <!-- see: http://interlink.codeplex.com/documentation -->
      <!--<add verb="GET,POST" path="FileDownload.ashx" type="HSS.Interlink.Web.FileDownload, HSS.Interlink.Web" />-->
      <!--<add verb="GET,POST" path="FileUpload.ashx" type="HSS.Interlink.Web.FileUpload, HSS.Interlink.Web" />-->
      
      [...]
    </httpHandlers>
    [...]
  </system.web>
  <system.codedom>
    <compilers>
      <compiler language="c#;cs;csharp" extension=".cs" warningLevel="4" type="Microsoft.CSharp.CSharpCodeProvider, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
        <providerOption name="CompilerVersion" value="v4.0" />
        <providerOption name="WarnAsError" value="false" />
      </compiler>
    </compilers>
  </system.codedom>
  <system.webServer>
    [...]
    <handlers>
      <!-- LightSwitch trace.axd handler -->
      <add name="LightSwitchTrace" path="trace.axd" verb="*" type="Microsoft.LightSwitch.WebHost.Implementation.TraceHandler,Microsoft.LightSwitch.Server.Internal,Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
      
      <!-- Upload HTTP Handlers -->
      <!-- used for real environment and IIS Integration Mode-->
      <!-- see: http://interlink.codeplex.com/documentation -->
      <add name="FileDownload" verb="GET,POST" path="FileDownload.ashx" type="HSS.Interlink.Web.FileDownload, HSS.Interlink.Web" />
      <add name="FileUpload" verb="GET,POST" path="FileUpload.ashx" type="HSS.Interlink.Web.FileUpload, HSS.Interlink.Web" />
    </handlers>
    [...]
  </system.webServer>
  <system.serviceModel>
    <serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
    <behaviors>
      <serviceBehaviors>
        <behavior>
          <dataContractSerializer maxItemsInObjectGraph="6553600" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
  </system.serviceModel>
</configuration>

 

 

I would be so happy if you could help me to get this application running.

- Simon

Apr 25, 2012 at 8:36 PM

Sure, glad to help.

Can you confirm, the production URL of your site/app is the root or app name?

Meaning, the FileUpload.ashx and the auto url resolution in the client assumes your url is http://domain.com/fileupload.ashx

If your published URL is http://domain.com/appname/fileupload.ashx then the you will have to update web config to be "appname/fileupload.ashx"

Apr 26, 2012 at 2:15 PM
Edited Apr 26, 2012 at 2:17 PM

Thanks for your answer. My url looks like "http://www.domain.de/OhmrolleManagement" (I replaced my domain with "domain"). So FileUpload.ashx, should work with "http://www.domain.de/OhmrolleManagement/FileUpload.ashx", right?

I changed the config like this:

<handlers>
      <!-- LightSwitch trace.axd handler -->
      <add name="LightSwitchTrace" path="trace.axd" verb="*" type="Microsoft.LightSwitch.WebHost.Implementation.TraceHandler,Microsoft.LightSwitch.Server.Internal,Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
      
      <!-- Upload HTTP Handlers -->
      <!-- used for real environment and IIS Integration Mode-->
      <!-- see: http://interlink.codeplex.com/documentation -->
      <add name="FileDownload" verb="GET,POST" path="OhmrolleManagement/FileDownload.ashx" type="HSS.Interlink.Web.FileDownload, HSS.Interlink.Web" />
      <add name="FileUpload" verb="GET,POST" path="OhmrolleManagement/FileUpload.ashx" type="HSS.Interlink.Web.FileUpload, HSS.Interlink.Web" />
</handlers>

Still, it does not seem to work. When I enter http://www.domain.de/OhmrolleManagement/FileUpload.ashx directly into my browser I get "HTTP Error 404.0 - Not Found"

The app runs in IIS 7.5 with Integrated Pipeline Mode.

By the way: Is it importand to comment the following code when publishing to IIS?

    <httpHandlers>
      <!-- Upload HTTP Handlers -->
      <!-- only used for Debugging and IIS Classic Mode, disable for IIS-->
      <!-- see: http://interlink.codeplex.com/documentation -->
      <!--<add verb="GET,POST" path="FileDownload.ashx" type="HSS.Interlink.Web.FileDownload, HSS.Interlink.Web" />-->
      <!--<add verb="GET,POST" path="FileUpload.ashx" type="HSS.Interlink.Web.FileUpload, HSS.Interlink.Web" />-->
      
      [...]
    </httpHandlers>

- Simon

Apr 26, 2012 at 2:29 PM

Yes, typically IIS will complain if it finds regular handlers when running in Integrated mode.

And you can't navigate to this URL because the handler is validating the input and so just returns that failure. In the next release, we've add a PING option, to allow navigating to the handler allowing you to confirm the URL is working as expected.

The only other issue probably is on the client side. The Interlink Client uses the current URL of the SL App to auto resolve the URL to the handler, so you may have to provide an absolute path of the FileUpload.ashx to the client.

var ddialog = new DownloadFileDialog(blah, blah)
ddialog.DownloadUri = "http://www.domain.de/OhmrolleManagement/FileUpload.ashx";

If you use IIS Express to test in your dev machine, you can configure it to mimic your prod environment to help run down these confguration nuances without having to do a full release.

Apr 27, 2012 at 6:57 PM

Also, just to confirm on the client side you can just set the DownloadUri to "fileupload.ashx" and the auto resolver should work. It's defaulted to "/fileupload.ashx" which assumes you're coming from the root url. So if you remove the "/" it will append it.

Let us know if you need anymore help.

Apr 27, 2012 at 8:47 PM

Thank your so much for your friendly and helpful support. It looks like it is working now :-). I will do some more testing next week. I will then reply if everything is working.

May 15, 2012 at 2:31 PM
Edited May 15, 2012 at 2:38 PM

It works now, thank you for your support! However I still have one last issue: When uploading bigger files (>1MB) an error occurs. The first chunks seem fine and definitely arrives at the server but after a few chunks the connection gets dropped.  Client returns this error "The remote server returned an error: NotFound.", the server does not return any error at all. Debugging with Fiddler I see that the first chunks arrive but after some requests later it fails with response code 500. The same error does not occur when a file is uploaded from the same machine (Windows Server 2008).

(maxsize is set to maximum on client side)

I wonder if this is an IIS- or Lightswitch specific error. I will check if the same error occurs when I use another Website for fileuploads and if it disappears when I increase the maxMessageSize.

May 15, 2012 at 3:42 PM

Glad to hear you got the one issue resolved.

As far as the new error, the error is the most likely the 'maxAllowedContentLength' which is new to IIS 7.

Interlink automatically adjusts it's chunk size to ensure it does not exceed the server side maxRequestLength setting in IIS but does not look at this new setting 'maxAllowedContentLength'. And so if you're leveraging the Metadata feature of Interlink you could exceed that setting.

Here's a good forum post that discusses the issue. http://forums.iis.net/t/1169846.aspx

Also regarding the server side, LS should not have anything to do with this, as the aspx handler is outside of LS. But the LS Client could potentially have some throttle or control I'm not aware of that restricts outbound requests.

May 15, 2012 at 4:13 PM

Thank you for this information. Changing "maxAllowedContentLength" didn't help. Also the number of chunks that are successfully uploaded varies. Anyway, I now created a seperate Web-Application that handles the uploades and this approach seams to work fine. It also works for big files without any extra configuration. This workaround has little overhead and works fine for me.

The last thing that I will look into is if I can use authentication without much overhead. I just want to ensure that only my SL-Lightswitch-Application can upload files and not anyone.

May 15, 2012 at 4:23 PM

For my own knowledge, and to confirm your solution, by simply hosting the Server Side handler (Interlink plus your File Handler) in a seperate Web App fixed the issue?

Also for security, there is a method in your upload file handler that you override and can leverage what ever authentication/authorization scheme you need.

/// <summary>
/// Validate the current request.
/// </summary>
/// <returns>true if the request is allowed; otherwise false</returns>
public abstract bool IsAuthorized();
This example below may not work given you separated out the handler from you main app (meaning there may not be a Context.User)
public override bool IsAuthorized()
{
    //Debug.WriteLine("IsAuthorized...");
    if (this.Context.User.Identity.IsAuthenticated)
    {
        if (this.Context.User.IsInRole("requiredRole"))
            return true;
    }
    return false;
}
May 15, 2012 at 4:59 PM

Yes, simply hosting the Server Side handler in a seperate Web App fixed the issue. I also did not change the web.config properties for messageSize etc., it just worked.

I will check if implementing security works the next days. Is there a way to ensure security without using this.Context.User.Identity? Maybe I can do something like calculating a hash-value and then transfer it to the server using Metadata and check there if it is valid.

May 15, 2012 at 5:04 PM

regarding security, yes, you can implement however works best for you. Just make sure you do it in the IsAuthorized override. Note, the IsAuthorized method is called for each request made (including the check for new file, cancel and complete in addition to each chunk request). Also, the chunks themselves are already hashed to ensure they're not tampered with while enroute but doesn't ensure authorization. You're idea of providing a  hash in the Metadata should work just fine though.

May 15, 2012 at 5:06 PM

Also, that confirms it has something to do with LS and not IIS. If I find anything more I will let you know.

Thanks for using Interlink!

May 15, 2012 at 5:17 PM

Thank you for the hints! Something I would really like to know is what kind of instance context mode you use and when you transfer the metadata. The GetFilePath()-method is called for every chunk. Would it make sense to compute the filename only at the first call and store it in a local property and then return it everytime GetFilePath() is called. I would suspect that the constructor or CreateNewFile() should be good places to run code that I have to run only once per upload.

May 15, 2012 at 5:31 PM

Since this is just a web request handler, there is no state (stateless). So you have to validate each web call and recalculate the file path. You could store it in a local file but then you'd have to pull it ouf of the file on each request which would take more time and resources versus just Path.Combine().

As far as Metadata, that is the first thing that is sent (it's the Prepare action and includes the metadata). Then the rest of the pipeline is processed (FileExists, CreateNew, AppendChunk(repeatedly), Complete and Cancel). IsAuthorized is called for each one except for Prepare. So again your idea for authorization should work.