Can I just use Office Web Apps Server

topcat3 picture topcat3 · Jun 12, 2013 · Viewed 10.1k times · Source

I am trying to have a document management system in browser with office. I have found this solution http://www.edrawsoft.com/officeviewer.php but it uses the client copy of office.

I would like to use Office Web Apps but my question is ... do I need to use it through SharePoint or other Microsoft products or can I just hook up a website to use Office Web Apps in browser for my own document system

Answer

thebitllc picture thebitllc · Jun 12, 2013

You can write your own server that implements WOPI protocol, this will support PPTX/XSLX in view/edit mode, DOCX/PDF in view mode only. WOPI server is pretty simple to implement.

To edit word docs you need to implement Cobalt or FSSHTTP/FSSHTTPB protocol.

Also don't forget licensing, Office Web Apps requires that all users have a valid office license.

Here is a sample .NET WOPI server:

Call it with:

http://OFFICEWEBAPPS.HOST/p/PowerPointFrame.aspx?PowerPointView=EditView&access_token=12345&WOPISrc=URLENCODED_URL_OF_THE_WOPI_SERVER

Like: http://WOPISERVER.HOST:2000/wopi/files/1.pptx

This will open 1.pptx in your c:\temp

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
using System.Runtime.Serialization;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using System.Web;
using System.IO;
using System.Runtime.Serialization.Json;

namespace WopiServerTutorial
{
    public class WopiServer
    {
        private HttpListener Listener;

        static void Main(string[] args)
        {
            WopiServer s = new WopiServer();
            s.Start();

            Console.WriteLine("A simple wopi webserver. Press a key to quit.");
            Console.ReadKey();

            s.Stop();
        }

        public void Start()
        {
            Listener = new HttpListener();
            Listener.Prefixes.Add(@"http://+:8080/");
            Listener.Start();
            Listener.BeginGetContext(ProcessRequest, Listener);
            Console.WriteLine(@"WopiServer Started");
        }

        public void Stop()
        {
            Listener.Stop();
        }

        private void ProcessRequest(IAsyncResult result)
        {
            HttpListener listener = (HttpListener)result.AsyncState;
            HttpListenerContext context = listener.EndGetContext(result);

            Console.WriteLine(@"Got a " + context.Request.HttpMethod  + " request for URL: " + context.Request.Url.PathAndQuery);
            var stringarr = context.Request.Url.AbsolutePath.Split('/');
            var rootDir = @"C:\\temp\\";

            if (stringarr.Length == 5 && context.Request.HttpMethod.Equals(@"GET"))
            {
                Console.WriteLine(@"Getting content for the file: " + rootDir + stringarr[3]);

                // get file's content
                var file = rootDir + stringarr[3];
                var stream = new FileStream(file, FileMode.Open);
                var fi = new FileInfo(file);

                context.Response.ContentType = @"application/octet-stream";
                context.Response.ContentLength64 = fi.Length;
                stream.CopyTo(context.Response.OutputStream);
                context.Response.Close();
            }
            //else if (stringarr.Length == 5 && context.Request.HttpMethod.Equals(@"POST"))
            //{
            //    // write
            //}
            else if (stringarr.Length == 4 && context.Request.HttpMethod.Equals(@"GET"))
            {
                Console.WriteLine(@"Getting metdata for the file: " + rootDir + stringarr[3]);
                var fi = new FileInfo(rootDir + stringarr[3]);

                CheckFileInfo cfi = new CheckFileInfo();
                cfi.AllowExternalMarketplace = false;
                cfi.BaseFileName = fi.Name;
                cfi.BreadcrumbBrandName = "";
                cfi.BreadcrumbBrandUrl = "";
                cfi.BreadcrumbDocName = "";
                cfi.BreadcrumbDocUrl = "";
                cfi.BreadcrumbFolderName = "";
                cfi.BreadcrumbFolderUrl = "";
                cfi.ClientUrl = "";
                cfi.CloseButtonClosesWindow = false;
                cfi.CloseUrl = "";
                cfi.DisableBrowserCachingOfUserContent = true;
                cfi.DisablePrint = true;
                cfi.DisableTranslation = true;
                cfi.DownloadUrl = "";
                cfi.FileUrl = "";
                cfi.FileSharingUrl = "";
                cfi.HostAuthenticationId = "s-1-5-21-3430578067-4192788304-1690859819-21774";
                cfi.HostEditUrl = "";
                cfi.HostEmbeddedEditUrl = "";
                cfi.HostEmbeddedViewUrl = "";
                cfi.HostName = @"SharePoint";
                cfi.HostNotes = @"HostBIEnabled";
                cfi.HostRestUrl = "";
                cfi.HostViewUrl = "";
                cfi.IrmPolicyDescription = "";
                cfi.IrmPolicyTitle = "";
                cfi.OwnerId = @"4257508bfe174aa28b461536d8b6b648";
                cfi.PresenceProvider = "AD";
                cfi.PresenceUserId = @"S-1-5-21-3430578067-4192788304-1690859819-21774";
                cfi.PrivacyUrl = "";
                cfi.ProtectInClient = false;
                cfi.ReadOnly = false;
                cfi.RestrictedWebViewOnly = false;
                cfi.SHA256 = "";
                cfi.SignoutUrl = "";
                cfi.Size = fi.Length;
                cfi.SupportsCoauth = false;
                cfi.SupportsCobalt = false;
                cfi.SupportsFolders = false;
                cfi.SupportsLocks = true;
                cfi.SupportsScenarioLinks = false;
                cfi.SupportsSecureStore = false;
                cfi.SupportsUpdate = true;
                cfi.TenantId = @"33b62539-8c5e-423c-aa3e-cc2a9fd796f2";
                cfi.TermsOfUseUrl = "";
                cfi.TimeZone = @"+0300#0000-11-00-01T02:00:00:0000#+0000#0000-03-00-02T02:00:00:0000#-0060";
                cfi.UserCanAttend = false;
                cfi.UserCanNotWriteRelative = false;
                cfi.UserCanPresent = false;
                cfi.UserCanWrite = true;
                cfi.UserFriendlyName = "";
                cfi.UserId = "";
                cfi.Version = @"%22%7B59CCD75F%2D0687%2D4F86%2DBBCF%2D059126640640%7D%2C1%22";
                cfi.WebEditingDisabled = false;

                // encode json
                var memoryStream = new MemoryStream();
                var json = new DataContractJsonSerializer(typeof(CheckFileInfo));
                json.WriteObject(memoryStream, cfi);
                memoryStream.Flush();
                memoryStream.Position = 0;
                StreamReader streamReader = new StreamReader(memoryStream);
                var jsonResponse = Encoding.UTF8.GetBytes(streamReader.ReadToEnd());

                context.Response.ContentType = @"application/json";
                context.Response.ContentLength64 = jsonResponse.Length;
                context.Response.OutputStream.Write(jsonResponse, 0, jsonResponse.Length);
                context.Response.Close();
            }
            else
            {
                byte[] buffer = Encoding.UTF8.GetBytes("");
                context.Response.ContentLength64 = buffer.Length;
                context.Response.ContentType = @"application/json";
                context.Response.OutputStream.Write(buffer, 0, buffer.Length);
                context.Response.OutputStream.Close();
            }

            Listener.BeginGetContext(ProcessRequest, Listener);
        }
    }

    [DataContract]
    public class CheckFileInfo
    {
        [DataMember]
        public bool AllowExternalMarketplace { get; set; }
        [DataMember]
        public string BaseFileName { get; set; }
        [DataMember]
        public string BreadcrumbBrandName { get; set; }
        [DataMember]
        public string BreadcrumbBrandUrl { get; set; }
        [DataMember]
        public string BreadcrumbDocName { get; set; }
        [DataMember]
        public string BreadcrumbDocUrl { get; set; }
        [DataMember]
        public string BreadcrumbFolderName { get; set; }
        [DataMember]
        public string BreadcrumbFolderUrl { get; set; }
        [DataMember]
        public string ClientUrl { get; set; }
        [DataMember]
        public bool CloseButtonClosesWindow { get; set; }
        [DataMember]
        public string CloseUrl { get; set; }
        [DataMember]
        public bool DisableBrowserCachingOfUserContent { get; set; }
        [DataMember]
        public bool DisablePrint { get; set; }
        [DataMember]
        public bool DisableTranslation { get; set; }
        [DataMember]
        public string DownloadUrl { get; set; }
        [DataMember]
        public string FileSharingUrl { get; set; }
        [DataMember]
        public string FileUrl { get; set; }
        [DataMember]
        public string HostAuthenticationId { get; set; }
        [DataMember]
        public string HostEditUrl { get; set; }
        [DataMember]
        public string HostEmbeddedEditUrl { get; set; }
        [DataMember]
        public string HostEmbeddedViewUrl { get; set; }
        [DataMember]
        public string HostName { get; set; }
        [DataMember]
        public string HostNotes { get; set; }
        [DataMember]
        public string HostRestUrl { get; set; }
        [DataMember]
        public string HostViewUrl { get; set; }
        [DataMember]
        public string IrmPolicyDescription { get; set; }
        [DataMember]
        public string IrmPolicyTitle { get; set; }
        [DataMember]
        public string OwnerId { get; set; }
        [DataMember]
        public string PresenceProvider { get; set; }
        [DataMember]
        public string PresenceUserId { get; set; }
        [DataMember]
        public string PrivacyUrl { get; set; }
        [DataMember]
        public bool ProtectInClient { get; set; }
        [DataMember]
        public bool ReadOnly { get; set; }
        [DataMember]
        public bool RestrictedWebViewOnly { get; set; }
        [DataMember]
        public string SHA256 { get; set; }
        [DataMember]
        public string SignoutUrl { get; set; }
        [DataMember]
        public long Size { get; set; }
        [DataMember]
        public bool SupportsCoauth { get; set; }
        [DataMember]
        public bool SupportsCobalt { get; set; }
        [DataMember]
        public bool SupportsFolders { get; set; }
        [DataMember]
        public bool SupportsLocks { get; set; }
        [DataMember]
        public bool SupportsScenarioLinks { get; set; }
        [DataMember]
        public bool SupportsSecureStore { get; set; }
        [DataMember]
        public bool SupportsUpdate { get; set; }
        [DataMember]
        public string TenantId { get; set; }
        [DataMember]
        public string TermsOfUseUrl { get; set; }
        [DataMember]
        public string TimeZone { get; set; }
        [DataMember]
        public bool UserCanAttend { get; set; }
        [DataMember]
        public bool UserCanNotWriteRelative { get; set; }
        [DataMember]
        public bool UserCanPresent { get; set; }
        [DataMember]
        public bool UserCanWrite { get; set; }
        [DataMember]
        public string UserFriendlyName { get; set; }
        [DataMember]
        public string UserId { get; set; }
        [DataMember]
        public string Version { get; set; }
        [DataMember]
        public bool WebEditingDisabled { get; set; }
    }
}

Update for June 13th 2014:

Using Cobalt API it's possible to edit word documents, though my implementation is far from perfect. Let's get with the basics. FSSHTTP requires Shredded Storage, SharePoint implemented in the database, but after looking around Cobalt Assembly I identified the way to create a shredded file on the file system... Here are the fragments of the relevant code, this first part takes word doc from c:\tmp\test.docx and converts to shredded blobs in c:\tmp\filestore and c:\tmp\wacupdate

            DisposalEscrow disposal = new DisposalEscrow("temp1");

            CobaltFilePartitionConfig content = new CobaltFilePartitionConfig();
            content.IsNewFile = false;
            content.HostBlobStore = new FileSystemHostBlobStore("C:\\tmp\\filestore\\", "filestore", new FileSystemHostBlobStore.Config(), disposal, true, false);
            content.cellSchemaIsGenericFda = true;
            content.CellStorageConfig = new CellStorageConfig();
            content.Schema = CobaltFilePartition.Schema.ShreddedCobalt;
            content.PartitionId = FilePartitionId.Content;

            CobaltFilePartitionConfig wacupdate = new CobaltFilePartitionConfig();
            wacupdate.IsNewFile = false;
            wacupdate.HostBlobStore = new FileSystemHostBlobStore("C:\\tmp\\wacstore\\", "wacstore", new FileSystemHostBlobStore.Config(), disposal, true, false);
            wacupdate.cellSchemaIsGenericFda = false;
            wacupdate.CellStorageConfig = new CellStorageConfig();
            wacupdate.Schema = CobaltFilePartition.Schema.ShreddedCobalt;
            wacupdate.PartitionId = FilePartitionId.WordWacUpdate;

            Dictionary<FilePartitionId, CobaltFilePartitionConfig> pd = new Dictionary<FilePartitionId, CobaltFilePartitionConfig>();
            pd.Add(FilePartitionId.Content, content);
            pd.Add(FilePartitionId.WordWacUpdate, wacupdate);

    // custom locking store is my implementation of hostlockingstore
            CobaltFile cobaltFile = new CobaltFile(disposal, pd, new CustomHostLockingStore(), null);

            var src = FileAtom.FromExisting("C:\\tmp\\Test.docx", disposal);
            Cobalt.Metrics o1;
            cobaltFile.GetCobaltFilePartition(FilePartitionId.Content).SetStream(RootId.Default.Value, src, out o1);
            cobaltFile.GetCobaltFilePartition(FilePartitionId.Content).GetStream(RootId.Default.Value).Flush();

            cobaltFile.CommitChanges();

Now that you have the cobaltFile you can edit my original wopi code with this:

        }
        else if (context.Request.HttpMethod.Equals(@"POST") && context.Request.Headers["X-WOPI-Override"].Equals("COBALT"))
        {
            Console.WriteLine(@"Got a cobalt request for the file");
            var ms = new MemoryStream();
            context.Request.InputStream.CopyTo(ms);
            AtomFromByteArray atomRequest = new AtomFromByteArray(ms.ToArray());
            RequestBatch requestBatch = new RequestBatch();

            Object ctx;
            ProtocolVersion protocolVersion;

            requestBatch.DeserializeInputFromProtocol(atomRequest, out ctx, out protocolVersion);

            cobaltFile.CobaltEndpoint.ExecuteRequestBatch(requestBatch);
            cobaltFile.CommitChanges();


            var response = requestBatch.SerializeOutputToProtocol(protocolVersion);

            context.Response.Headers.Add("X-WOPI-MachineName", "test");
            context.Response.Headers.Add("X-WOPI-CorellationID", context.Request.Headers["X-WOPI-CorrelationID"]);
            context.Response.Headers.Add("request-id", context.Request.Headers["X-WOPI-CorrelationID"]);
            context.Response.ContentType = @"application/octet-stream";
            context.Response.ContentLength64 = response.Length;
            response.CopyTo(context.Response.OutputStream);
            context.Response.Close();

        }

and customlockingstore coming soon. Converting from shredded back to the word document I haven't done yet, but I think I know how

the final update - the complete solution is here:

https://github.com/thebitllc/WopiBasicEditor