Using GroupDocs in ASP.NET Core Linux Docker

Hi,

I’m evaluating GroupDocs Viewer for our application which using ASP.NET Core in Docker environment (Linux).

The sample for ASP.NET Core is very basic, and all are done via middleware. There are few requirements which I need your guidance on how to do it:

  1. How to configure the Viewer. For example, our file source is MemoryStream, and we’d like to hide the “Browse files” icon, and Download icon.

  2. As mentioned above, our file source is MemoryStream, how to do this in ASP.NET Core? It’s the same with below link, but the provided solution is in MVC.
    How to view file in viewer without downloading and saving to server file system - GroupDocs.Viewer Product Family - Free Support Forum - groupdocs.com

Thank you.

@Bipo

Thank your interest in GroupDocs.Viewer.

It can be done via configuration in the Program.cs file

builder.Services
    .AddGroupDocsViewerUI(config => 
        config
         .DisableFileBrowsing()
         .DisableFileDownload());

You can also find the source code of the middleware used in ASP.NET Core Demo at https://github.com/groupdocs-viewer/GroupDocs.Viewer-for-.NET-UI so you may have full control over the server and client part of the application.

You can use any file storage. The example can be found in this Sample Application. I’ve cut the most important parts to give you an idea of how it can be configured.

using GroupDocs.Viewer;
using GroupDocs.Viewer.UI.Core;
using GroupDocs.Viewer.UI.Core.Entities;
using GroupDocs.Viewer.UI.SelfHost.Api;

...

public void ConfigureServices(IServiceCollection services)
{
  services.AddTransient<IFileStorage, MyFileStorage>();
  services.AddTransient<IFileTypeResolver, MyFileTypeResolver>();

...

class MyFileStorage : IFileStorage
{
    public Task<IEnumerable<FileSystemEntry>> ListDirsAndFilesAsync(string dirPath)
    {
        throw new NotImplementedException();   
    }
    public Task<byte[]> ReadFileAsync(string filePath)
    {
        // Read file from any storage here
        return File.ReadAllBytesAsync("./Storage/sample.docx");
    }
    public Task<string> WriteFileAsync(string fileName, byte[] bytes, bool rewrite)
    {
        throw new NotImplementedException();
    }
}
class MyFileTypeResolver : IFileTypeResolver
{
    public Task<FileType> ResolveFileTypeAsync(string filePath)
    {
        // resolve file type by file path
        return Task.FromResult<FileType>(FileType.DOCX);
    }
}

Since you’re going to read the file from your custom storage you can implement IFileTypeResolver to avoid automatic file type detection based on magic numbers and file contents analysis.

Thank you for your quick response Vladimir, really appreciate it.

I’ve run the sample app and roughly get the idea. I will study it more on how to gel with our application design.

Probably one more doubt, regarding the cache. If it’s required, is there a way for “auto” cleanup? We intend to render document as a whole (all pages).

Thank you again.

@Bipo

You’re welcome! Auto cleanup is not implemented for the file cache, so the files are always there. You can use In Memory Cache where you can specify the lifetime for cache entries (link to the source code).

builder.Services
    .AddControllers()
    .AddGroupDocsViewerSelfHostApi()
    .AddLocalStorage("./Storage/Files")
    .AddInMemoryCache(config =>
    {
        const int cacheEntryExpirationTimeMinutes = 15;

        config.SetGroupCacheEntriesByFile(true);
        config.SetCacheEntryExpirationTimeoutMinutes(cacheEntryExpirationTimeMinutes);
    });

Awesome :slight_smile: Will look into these.

Thank you Vladimir.

@Bipo

You’re welcome!

Hi Vladimir,

I’m using the Sample Application (sample-viewer-app) that you sent, and trying to view PDF document. I’ve modified the source file to a PDF file, and also the FileTypeResolver to use FileType.PDF. But I encountered error from Aspose.PDF:

System.NullReferenceException
HResult=0x80004003
Message=Object reference not set to an instance of an object.
Source=Aspose.PDF
StackTrace:
at #=z53DiufXzQJ_xboSmAwF1lzdRU5nPOMxgig==.#=z_ul7S4OCg2c0bpYl6w==(#=z7DkZdtMHbbsf$k1nHCBKOt6FbULB #=zZ0x0cFo=)

Also, is there way to set Viewer’s language? I see there’s Supported and Default language. But since our application is multi language, we’d like to set the language on each document viewing.

Please advise on these.

Thank you.

@Bipo

Can you share the app that you’ve got and the sample file you’re trying to view? Which environment you’re running the app?

Such a feature is not supported out of the box. But you can make a custom endpoint e.g. /my-viewer-config where you can decide which language is the default. Not that you have to keep config in AddGroupDocsViewerUI and your custom one in sync (Sample App).

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    app
        .UseRouting()
        .UseEndpoints(endpoints =>
        {
            endpoints.MapControllerRoute(name: "default",
                pattern: "{controller=Home}/{action=Index}/{id?}");

            endpoints.MapGet("/my-viewer-config", async context =>
            {
                var config = GetConfig(Language.Italian);
                var json = SerializeToJson(config);

                context.Response.ContentType = "application/json";
                await context.Response.WriteAsync(json);
            });

            endpoints.MapGroupDocsViewerUI(options =>
            {
                options.UIPath = "/viewer";
                options.UIConfigEndpoint = "/my-viewer-config";
                options.APIEndpoint = "/viewer-api";
            });

            endpoints.MapGroupDocsViewerApi(options =>
            {
                options.ApiPath = "/viewer-api";
            });
        });
}

private object GetConfig(Language language)
{
    var config = new
    {
        pageSelector = true,
        download = false,
        upload = false,
        print = true,
        browse = false,
        rewrite = false,
        enableRightClick = true,
        defaultDocument = "",
        preloadPageCount = 3,
        zoom = true,
        search = true,
        thumbnails = true,
        htmlMode = true,
        printAllowed = true,
        rotate = false,
        saveRotateState = false,
        showLanguageMenu = false,
        defaultLanguage = language.Code,
        supportedLanguages = new string[]
        {
            language.Code
        }
    };

    return config;
}

private string SerializeToJson(object obj)
{
    JsonSerializerSettings jsonSerializationSettings = new JsonSerializerSettings
    {
        ContractResolver = new CamelCasePropertyNamesContractResolver()
    };

    string json = JsonConvert.SerializeObject(obj, jsonSerializationSettings);

    return json;
}

Hi Vladimir,

For the first issue of loading PDF file, it’s working on other machines.
As for the language config, yes this solution also works.

Now we have another issue. Our application have certain routing codes for different clients. So the pattern is like “/{routecode}/viewer” . However the {routecode} is read as plain string instead of pattern.
The static resources are redirected to here:
/%7Broutecode%7D/viewer/runtime.js
/%7Broutecode%7D/viewer/polyfills.js
/%7Broutecode%7D/viewer/main.js

instead of:
/ABC/viewer/runtime.js
/ABC/viewer/polyfills.js
/ABC/viewer/main.js

Please advise on how to achieve this.

@Bipo

This issue has been fixed in this commit. To fix the issue try updating GroupDocs.Viewer.UI package to 6.0.2.

<PackageReference Include="GroupDocs.Viewer.UI" Version="6.0.2" />

Hi Vladimir,

Still encounter the same error. I believe it’s because the index.html content, is still using the plain string itself. Please refer to my screenshot document (I run GroupDocs.Viewer.UI sample and just change the endpoints mapping to

        endpoints.MapGroupDocsViewerUI(options =>
        {
            options.UIPath = "/{routecode}/viewer";
            options.APIEndpoint = "/{routecode}/viewer-api";
        });
        endpoints.MapGroupDocsViewerApi(options =>
        {
            options.ApiPath = "/{routecode}/viewer-api";
        });

Routing.docx (156.6 KB)

@Bipo

The issue has been fixed for UIPath, so it is supports route templates with parameters. In case you keep APIEndpoint and ApiPath static it would work. Please check this viewer-net-ui.zip (2.5 KB) sample app as a demonstration.

var app = builder.Build();

app
    .UseRouting()
    .UseEndpoints(endpoints =>
    {
        endpoints.MapGroupDocsViewerUI(options =>
        {
            options.UIPath = "/{code}/viewer";
            options.APIEndpoint = "/viewer-api";
        });
        endpoints.MapGroupDocsViewerApi(options =>
        {
            options.ApiPath = "/viewer-api";
        });
    });

app.Run();

The APIEndpoint value is passed to the Angular app through a global variable.

Hi Vladimir,

The main “viewer” is successfully loaded, yes, because it’s the first hit. But when the viewer load, the HTML base is set to literal string of uipath
image.png (2.7 KB)
which then will request to /%7Bcode%7D/viewer/runtime.js, instead of /AAA/viewer/runtime.js
image.png (6.4 KB)

In our case, this routecode is used by load balancer to route to respective server the routecode belongs to (we also need the same for other paths and endpoint). Otherwise the request will not reach the web server.

@Bipo

Thank you for adding the details, now I see the issue. We’ll take a look at how this issue can be fixed and update you.

@Bipo

This issue has been fixed in GroupDocs.Viewer.UI 6.0.3.

<PackageReference Include="GroupDocs.Viewer.UI" Version="6.0.3" />

In case there are parameters they are replaced with actual route values in the main HTML file.