-1

私が働いている組織のために、さまざまなレコードに関連する一連の9つの異なるワークフローを提供するドキュメント管理システムを作成するように依頼されました。ワークフローの中には、「ファイル」またはレコードにドキュメントを追加し、ビジネスルールに基づいてこれらのドキュメントのサブセットを公開Webサイトに公開することが含まれます。

文書はほとんど例外なくPDF形式であり、通常、1つのレコードに対して一度に処理されるのは20未満です。

これをWebアプリケーションとして構築する主な理由は、リモートサイトで接続速度が遅くなる可能性がある場所間でファイルをコピーしたり、コピーしたりするのではなく、データセンターと高速スイッチにファイルを保持することでした。

システムは、より大きな一連のドキュメント(合計で329MBのサイズの114のPDFドキュメント)が途中で約95%タイムアウトするまで、問題なく機能していました。

コードは次のとおりです(IncomingDocumentsのタイプはList <FileInfo>です)-

List<string> filesSuccessfullyAdded = new List<string>();

foreach (FileInfo incomingFile in IncomingDocuments)
{
    FileOperations.AddDocument(incomingFile, false, ApplicationCode, (targetDirectoryPath.EndsWith(@"\") ? targetDirectoryPath : targetDirectoryPath + @"\"));
    FileInfo copiedDocument = new FileInfo(Path.Combine(targetDirectoryPath, incomingFile.Name));
    if (copiedDocument.Exists && copiedDocument.Length == incomingFile.Length && copiedDocument.LastWriteTime == incomingFile.LastWriteTime)
    {
        filesSuccessfullyAdded.Add(copiedDocument.Name);
    }
}

if (filesSuccessfullyAdded.Any())
{
    SetupConfirmationLiteral.Text += "<p class='info'>The following files have been successfully added to the application file-</p>";

    XDocument successfullyAddedList = new XDocument(
    new XElement("ul", new XAttribute("class", "documentList")));

    foreach (string successfulFile in filesSuccessfullyAdded)
    {
        successfullyAddedList.Root.Add(new XElement("li", successfulFile));
    }

    SetupConfirmationLiteral.Text += successfullyAddedList.ToString();
}

var notSuccessfullyAdded = from FileInfo incomingDocument in IncomingDocuments
                            where !filesSuccessfullyAdded.Contains(incomingDocument.Name)
                            orderby incomingDocument.Name ascending
                            select incomingDocument.Name;

if (notSuccessfullyAdded.Any())
{
    SetupConfirmationLiteral.Text += "<p class='alert'>The following files have <strong>not</strong> been successfully added to the application file-</p>";

    XDocument notAddedList = new XDocument(
    new XElement("ul", new XAttribute("class", "documentList")));

    foreach (string notAdded in notSuccessfullyAdded)
    {
        notAddedList.Root.Add(new XElement("li", notAdded));
    }

    SetupConfirmationLiteral.Text += notAddedList.ToString();

    SetupConfirmationLiteral.Text += "<p>A file of the same name may already exist in the target location.</p>";
}

の効用法で-

public static void AddDocument(FileInfo sourceFile, bool appendName, string applicationCode, string targetPath)
{
    try
    {
        DirectoryInfo targetDirectory = new DirectoryInfo(targetPath);
        if (targetDirectory.Exists)
        {
            string targetFileName = (appendName ? sourceFile.Name.Insert(sourceFile.Name.IndexOf(sourceFile.Extension, StringComparison.Ordinal), " UPDATED") : sourceFile.Name);
            if (targetDirectory.GetFiles(targetFileName).Any())
            {
                //Do not throw an exception if the file already exists. Silently return. If the file exists and matches both last modified and size it won't be reported, and can be archived as normal,
                //otherwise it should be reported to user in the calling method.
                return;
            }
            string targetFileUnc = Path.Combine(targetPath, targetFileName);
            sourceFile.CopyTo(targetFileUnc, overwrite: false);
            Logging.FileLogEntry(username: (HttpContext.Current.User.Identity.IsAuthenticated ? HttpContext.Current.User.Identity.Name : "Unknown User"), eventType: LogEventType.AddedDocument,
                applicationCode: applicationCode, document: sourceFile.Name, uncPath: targetFileUnc);
        }
        else
        {
            throw new PdmsException("Target directory does not exist");
        }
    }
    catch (UnauthorizedAccessException ex)
    {
        throw new PdmsException("Access was denied to the target directory. Contact the Service Desk.", ex);
    }
    catch (PathTooLongException)
    {
        throw new PdmsException(string.Format("Cannot add document {0} to the Site File directory for Application {1} - the combined path is too long. Use the Add Documents workflow to re-add documents to this Site File after renaming {0} to a shorter name.", sourceFile.Name, applicationCode ));
    }
    catch (FileNotFoundException ex)
    {
        throw new PdmsException("The incoming file was not found. It may have already been added to the application file.", ex);
    }
    catch (DirectoryNotFoundException ex)
    {
        throw new PdmsException("The source or the target directory were not found. The document(s) may have already been added to the application file.", ex);
    }
    catch (IOException ex)
    {
        throw new PdmsException("Error adding files - file(s) may be locked or there may be server or network problem preventing the copy. Contact the Service Desk.", ex);
    }
}

実際のコピーと監査を行うため。PdmsExceptionは、ユーザーに役立つエラーメッセージを表示するために使用する特定の例外クラスであり、ユーザーが可能な場合は独自の問題を解決できるようにするか、少なくとも失敗の理解可能な理由を示します。

ExecutionTimeoutプロパティ(http://msdn.microsoft.com/en-us/library/system.web.configuration.httpruntimesection.executiontimeout.aspx)をデフォルトの110秒(最大300秒)を超えて増やすことができることはわかっています。 -これはおそらく、この場合はタイムアウトが発生しなくなることを意味しますが、ユーザーが何千ものドキュメントを追加または公開しようとするとどうなりますか。このソリューションは適切に拡張できず、問題を解決するのではなく、単に延期します。

Visual Studio2010で.NET4を使用しているため、私が知る限り、非同期のサードパーティ実装を使用し、AsyncBridge(https://nuget.org/packages/AsyncBridge)のように待機する必要があります。ドキュメントを作成し、ajaxを使用して進捗状況を更新します。Microsoftが提供する非同期ターゲティングパックを使用するために、VisualStudio2012またはXPよりも新しいWindowsにアクセスできません。

これらの制約がある場合、タイムアウトを回避し、(理想的には)各バッチが追加されるときにユーザーにフィードバックを提供するために、これらのドキュメントを分割/バッチ処理する方法はありますか?実装が簡単であれば、F#を検討することもできます。または、これについてVisual Studio 2012のケースを主張する必要がありますか?

4

1 に答える 1

9

まったく別の言語に移行したり、IDEツールをアップグレードしたりする必要はありません。それは当面の問題ではありません。目前の問題は、迅速な応答のために基本的に設計されたシステム(Webアプリケーション)が長時間実行されるプロセスに使用されていることです。

Webアプリケーションでは、数分以上かかるものはすべて非同期で実行する必要があります。HTTPの要求/応答モデルでは、(いくつかの理由で)要求を行うクライアントに迅速に応答することが最善です。

長時間実行されるプロセスの場合、「非同期」とは、AJAXを使用することを意味するものではありません。これは、他のプロセスと同様に、依然として要求/応答であるためです。

この場合の「非同期」とは、CPUを集中的に使用するタスクを処理する別のサーバー側プロセスが必要であり、Webアプリケーションは実行のためにタスクをキューに入れ、タスクのステータスを確認するだけです。人々はそれを探します。次に、タスクの完了後にタスクの結果を報告できます。

したがって、アーキテクチャの基本的な概要は次のようになります。

  • Webアプリケーションのユーザーがボタンをクリックして、「タスクを開始」します。
  • Webアプリケーションは、タスクがキューに入れられたことを示すレコードをデータベーステーブルに挿入します(おそらく、タスクをキューに入れたユーザーID、タイムスタンプ、その他の必要な情報が含まれます)。
  • 別のスケジュールされたアプリケーション(コンソールアプリケーションまたはWindowsサービスの可能性が高い)が永続的に実行されています。(常に実行されているWindowsサービスでタイマーを使用するか、コンソールアプリケーションとして数分ごとなどに繰り返し実行するようにスケジュールされています。)このアプリケーションは、データベーステーブルで新しいキューに入れられたタスクをチェックします。
  • アプリケーションはタスクを認識すると、データベースで「開始済み」としてマークを付け(したがって、アプリケーションの後続の実行では同じタスクを並行して実行しようとはしません)、実行を開始します。
  • Webアプリケーションは、データベーステーブル内のタスクのステータスを確認し、それを要求したユーザーに表示できるため、ユーザーはタスクがまだ実行中であることを確認できます。
  • タスクが完了すると、データベーステーブルのタスクレコードが更新され、結果がどこかに保存されます。(結果によって異なります。データ?データベース内。ある種のレポートファイル?どこかにファイルとして保存します。それはすべてあなた次第です。)
  • Webアプリケーションは、完了したタスクのステータスと記録されたその他の情報を確認でき、ユーザーはタスクの出力の表示を要求できます。

ここで覚えておくべき主なことは、責任を2つのアプリケーションに分割することです。Webアプリケーションは、ユーザーインターフェイスを提供することを目的としています。Webアプリケーションは、長時間実行されるバックグラウンドタスクには適していません。そのため、その責任は、その目的により適した別のアプリケーションに移されます。2つのアプリケーションは、共有データベースを介して調整します。

したがって、質問の最後に示すように、アプリケーションでタスクを単に「キューに入れる」ことができます(そしてそうすべきです)。ユーザーが適切と考えるさまざまな方法で、そのキューを管理します。

于 2013-03-26T17:30:09.997 に答える