Opening a Workshared Document

Opening a Workshared Document

The Application.OpenDocumentFile(ModelPath, OpenOptions) method can be used to set options related to opening a workshared document. In addition to options to detach from the central document or to allow a local file to be opened ReadOnly by a user other than its owner, options may also be set related to worksets. When a workshared document is opened, all system worksets are automatically opened, however user-created worksets can be specified to be opened or closed. Elements in an open workset can be expanded and displayed. However, elements in a closed workset are not displayed to avoid expanding them. By only opening worksets necessary in the current session, Revit's memory footprint is reduced, which can help with performance.

In the example below, a document is opened with two worksets specified to be opened. Note that the WorksharingUtils.GetUserWorksetInfo() method can be used to access workset information from a closed Revit document.

Code Region: Open Workshared Document

Document OpenDocumentWithWorksets(Application app, ModelPath projectPath)
{
    Document doc = null;
    try
    {
        // Get info on all the user worksets in the project prior to opening
        IList<WorksetPreview> worksets = WorksharingUtils.GetUserWorksetInfo(projectPath);
        IList<WorksetId> worksetIds = new List<WorksetId>();
        // Find two predetermined worksets
        foreach (WorksetPreview worksetPrev in worksets)
        {
            if (worksetPrev.Name.CompareTo("Workset1") == 0 ||
                worksetPrev.Name.CompareTo("Workset2") == 0)
            {
                worksetIds.Add(worksetPrev.Id);
            }
        }

        OpenOptions openOptions = new OpenOptions();
        // Setup config to close all worksets by default
        WorksetConfiguration openConfig = new WorksetConfiguration(WorksetConfigurationOption.CloseAllWorksets);
        // Set list of worksets for opening 
        openConfig.Open(worksetIds);
        openOptions.SetOpenWorksetsConfiguration(openConfig);
        doc = app.OpenDocumentFile(projectPath, openOptions);
    }
    catch (Exception e)
    {
        TaskDialog.Show("Open File Failed", e.Message);
    }

    return doc;
}

Another option is to open the document while just opening the last viewed worksets.

Code Region: Open last viewed worksets

public static Document OpenLastViewed(UIApplication uiApplication)
{
    Application application = uiApplication.Application;

    // Setup options
    OpenOptions options1 = new OpenOptions();

    // Default config opens all.  Close all first, then open last viewed to get the correct settings.
    WorksetConfiguration worksetConfig = new WorksetConfiguration(WorksetConfigurationOption.OpenLastViewed);
    options1.SetOpenWorksetsConfiguration(worksetConfig);

    // Open the document
    Document openedDoc = application.OpenDocumentFile(GetWSAPIModelPath("WorkaredFileSample.rvt"), options1);

    return openedDoc;
}

private static ModelPath GetWSAPIModelPath(string fileName)
{
    // Utility to get a local path for a target model file
    FileInfo filePath = new FileInfo(Path.Combine(@"C:\Documents\Revit Projects", fileName));
    ModelPath mp = ModelPathUtils.ConvertUserVisiblePathToModelPath(filePath.FullName);

    return mp;
}
The following two examples demonstrate how to create a new local first from disk or from a Revit server, and then open it. Note that the sample below uses the GetWSAPIModelPath() method used in the previous example.

Code Region: Open new local from disk

public static Document OpenNewLocalFromDisk(UIApplication uiApplication)
{
    // Create new local from a disk location
    ModelPath newLocalPath = GetWSAPIModelPath("LocalWorksharing.rvt");
    return (OpenNewLocalFromModelPath(uiApplication.Application, GetWSAPIModelPath("NewLocalWorksharing.rvt"), newLocalPath));
}


private static Document OpenNewLocalFromModelPath(Application app, ModelPath centralPath, ModelPath localPath)
{
    // Create the new local at the given path
    WorksharingUtils.CreateNewLocal(centralPath, localPath);

    // Select specific worksets to open
    // First get a list of worksets from the unopened document
    IList<WorksetPreview> worksets = WorksharingUtils.GetUserWorksetInfo(localPath);
    List<WorksetId> worksetsToOpen = new List<WorksetId>();

    foreach (WorksetPreview preview in worksets)
    {
        // Match worksets to open with criteria
        if (preview.Name.StartsWith("O"))
            worksetsToOpen.Add(preview.Id);
    }

    // Setup option to open the target worksets
    // First close all, then set specific ones to open
    WorksetConfiguration worksetConfig = new WorksetConfiguration(WorksetConfigurationOption.CloseAllWorksets);
    worksetConfig.Open(worksetsToOpen);

    // Open the new local
    OpenOptions options1 = new OpenOptions();
    options1.SetOpenWorksetsConfiguration(worksetConfig);
    Document openedDoc = app.OpenDocumentFile(localPath, options1);

    return openedDoc;
}
The following example uses the OpenNewLocalFromModelPath() method demonstrated as part of the previous example.

Code Region: Open new local from Revit Server

/// <summary>
/// Get the server path for a particular model and open a new local copy
/// </summary>
public static Document OpenNewLocalFromServer(UIApplication uiApp)
{
    // Create new local from a server location
    Application app = uiApp.Application;

    // Get the host id/IP of the server
    String hostId = app.GetRevitServerNetworkHosts().First();

    // try to get the server path for the particular model on the server
    String rootFolder = "|";
    ModelPath serverPath = FindWSAPIModelPathOnServer(app, hostId, rootFolder, "WorksharingOnServer.rvt");

    ModelPath newLocalPath = GetWSAPIModelPath("WorksharingLocalFromServer.rvt");
    return (OpenNewLocalFromModelPath(uiApp.Application, serverPath, newLocalPath));
}

/// <summary>
/// Uses the Revit Server REST API to recursively search the folders of the Revit Server for a particular model.
/// </summary>
private static ModelPath FindWSAPIModelPathOnServer(Application app, string hostId, string folderName, string fileName)
{
    // Connect to host to find list of available models (the "/contents" flag)
    XmlDictionaryReader reader = GetResponse(app, hostId, folderName + "/contents");
    bool found = false;

    // Look for the target model name in top level folder
    List<String> folders = new List<String>();
    while (reader.Read())
    {
        // Save a list of subfolders, if found
        if (reader.NodeType == XmlNodeType.Element && reader.Name == "Folders")
        {
            while (reader.Read())
            {
                if (reader.NodeType == XmlNodeType.EndElement && reader.Name == "Folders")
                    break;

                if (reader.NodeType == XmlNodeType.Element && reader.Name == "Name")
                {
                    reader.Read();
                    folders.Add(reader.Value);
                }
            }
        }
        // Check for a matching model at this folder level
        if (reader.NodeType == XmlNodeType.Element && reader.Name == "Models")
        {
            found = FindModelInServerResponseJson(reader, fileName);
            if (found)
                break;
        }
    }

    reader.Close();

    // Build the model path to match the found model on the server
    if (found)
    {
        // Server URLs use "|" for folder separation, Revit API uses "/"
        String folderNameFragment = folderName.Replace('|', '/');

        // Add trailing "/" if not present
        if (!folderNameFragment.EndsWith("/"))
            folderNameFragment += "/";

        // Build server path
        ModelPath modelPath = new ServerPath(hostId, folderNameFragment + fileName);
        return modelPath;
    }
    else
    {
        // Try subfolders
        foreach (String folder in folders)
        {
            ModelPath modelPath = FindWSAPIModelPathOnServer(app, hostId, folder, fileName);
            if (modelPath != null)
                return modelPath;
        }
    }

    return null;
}

// This string is different for each RevitServer version
private static string s_revitServerVersion = "/RevitServerAdminRESTService2014/AdminRESTService.svc/";

/// <summary>
/// Connect to server to get list of available models and return server response
/// </summary>
private static XmlDictionaryReader GetResponse(Application app, string hostId, string info)
{
    // Create request	
    WebRequest request = WebRequest.Create("http://" + hostId + s_revitServerVersion + info);
    request.Method = "GET";

    // Add the information the request needs

    request.Headers.Add("User-Name", app.Username);
    request.Headers.Add("User-Machine-Name", app.Username);
    request.Headers.Add("Operation-GUID", Guid.NewGuid().ToString());

    // Read the response
    XmlDictionaryReaderQuotas quotas =
        new XmlDictionaryReaderQuotas();
    XmlDictionaryReader jsonReader =
        JsonReaderWriterFactory.CreateJsonReader(request.GetResponse().GetResponseStream(), quotas);

    return jsonReader;
}

/// <summary>
/// Read through server response to find particular model
/// </summary>
private static bool FindModelInServerResponseJson(XmlDictionaryReader reader, string fileName)
{
    // Read through entries in this section
    while (reader.Read())
    {
        if (reader.NodeType == XmlNodeType.EndElement && reader.Name == "Models")
            break;

        if (reader.NodeType == XmlNodeType.Element && reader.Name == "Name")
        {
            reader.Read();
            String modelName = reader.Value;
            if (modelName.Equals(fileName))
            {
                // Match found, stop looping and return
                return true;
            }
        }
    }

    return false;
}

Open detached from central

If an add-in will be working on a workshared file but does not need to make permanet changes, it can open the model detached from the central file.

Code Region: Open detached

     
private static Document OpenDetached(Application application, ModelPath modelPath)
{
    OpenOptions options1 = new OpenOptions();

    options1.DetachFromCentralOption = DetachFromCentralOption.DetachAndDiscardWorksets;
    Document openedDoc = application.OpenDocumentFile(modelPath, options1);

    return openedDoc;
}
If an application only needs read-only access to a server file, the example below demonstrates how to copy the server model locally and open it detached. Note this code sample re-uses methods demonstrated in previous examples.

Code Region: Copy and open detached

public static Document CopyAndOpenDetached(UIApplication uiApp)
{
    // Copy a server model locally and open detached
    Application application = uiApp.Application;
    String hostId = application.GetRevitServerNetworkHosts().First();

    // Try to get the server path for the particular model on the server
    String rootFolder = "|";
    ModelPath serverPath = FindWSAPIModelPathOnServer(application, hostId, rootFolder, "ServerModel.rvt");

    // For debugging
    String sourcePath = ModelPathUtils.ConvertModelPathToUserVisiblePath(serverPath);

    // Setup the target location for the copy
    ModelPath localPath = GetWSAPIModelPath("CopiedModel.rvt");

    // Copy, allowing overwrite
    application.CopyModel(serverPath, ModelPathUtils.ConvertModelPathToUserVisiblePath(localPath), true);

    // Open the copy as detached
    Document openedDoc = OpenDetached(application, localPath);

    return openedDoc;
}