Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to use dynamic set of cores #614

Open
dnalneh opened this issue Jun 7, 2023 · 2 comments
Open

How to use dynamic set of cores #614

dnalneh opened this issue Jun 7, 2023 · 2 comments

Comments

@dnalneh
Copy link

dnalneh commented Jun 7, 2023

We have been using Solr.Net for a number of years and now have a new requirement: When the system is started, some new Solr cores may be dynamically created, but they should use the same data type (keyword: multi-tenancy).

So far we have used the following approach with access to exactly one core:
services.AddSolrNet("http://solr:8983/solr/main_core");
services.AddScoped<ISolrService, SolrService<SolrTreeNode, ISolrOperations>>();

How could the new requirement be implemented with Solr.Net?
We will then have many cores, each of type SolrTreeNode:

@dnalneh
Copy link
Author

dnalneh commented Jun 9, 2023

Today, I implemented a possible solution for the requirement, which seems to work at first sight. Well, it will be integrated into a multi-container-based and heavily used web application.
Maybe someone can give an estimation if the solution can run correctly and fast, especially when using SolrConnection instead of AutoSolrConnection.

services.AddSolrNet("http://solr:8983/solr/not_relevant"); // url not relevant, will be changed later dynamically
services.AddScoped<ISolrService, SolrService<SolrTreeNode, ISolrOperations>>(); // unchanged

// new things
HttpWebRequestFactory factory = new HttpWebRequestFactory();
services.Replace(new ServiceDescriptor(
         typeof(ISolrInjectedConnection<SolrTreeNode>),
         s =>
         {
              var connection = new MultiCoreSolrConnection(new SolrConnection("http://solr:8983/solr/main_core_", factory), s.GetRequiredService<IHttpContextAccessor>());
              return new BasicInjectionConnection<SolrTreeNode>(connection);
          },
           ServiceLifetime.Scoped // Important: No Singleton!
       ));

And this is the intercepting class, that reads out the host header via _httpContextAccessor.HttpContext, which is neccessary for finding the tenant-Id/contentTreeId.

public class MultiCoreSolrConnection : ISolrConnection
    {
        readonly SolrConnection _connection;
        readonly IHttpContextAccessor _httpContextAccessor;

        public MultiCoreSolrConnection(ISolrConnection connection, IHttpContextAccessor httpContextAccessor)
        {
            _connection = connection as SolrConnection;            
            _httpContextAccessor = httpContextAccessor;
        }

        void ReplaceUrl()
        {
            string host = _httpContextAccessor.HttpContext.Request.Host.Value;
            string contentTreeId = LocalSettingsHelper.GetSetting<int>(host, LocalSettingsEnum.ContentTreeId).ToString();
            
            // usual case
            if (_connection.ServerURL.EndsWith("_"))
            {
                _connection.ServerURL += contentTreeId;
            }
            // should not happen, but must be ruled out for safety
            else if (!_connection.ServerURL.EndsWith("_" + contentTreeId))
            {
                throw new Exception("MultiCoreSolrConnection - ServerURL critical value");
            }
            else
            {
                // can happen, ServerUrl is already set correctly, do nothing
            }
        }

        public string Post(string relativeUrl, string s)
        {
            ReplaceUrl();
            return _connection.Post(relativeUrl, s);
        }

        public async Task<string> PostAsync(string relativeUrl, string s)
        {
            ReplaceUrl();
            return await _connection.PostAsync(relativeUrl, s);
        }

        public string PostStream(string relativeUrl, string contentType, Stream content, IEnumerable<KeyValuePair<string, string>> getParameters)
        {
            ReplaceUrl();
            return _connection.PostStream(relativeUrl, contentType, content, getParameters);
        }

        public async Task<string> PostStreamAsync(string relativeUrl, string contentType, Stream content, IEnumerable<KeyValuePair<string, string>> getParameters)
        {            
            ReplaceUrl();
            return await _connection.PostStreamAsync(relativeUrl, contentType, content, getParameters);
        }

        public string Get(string relativeUrl, IEnumerable<KeyValuePair<string, string>> parameters)
        {            
            ReplaceUrl();
            return _connection.Get(relativeUrl, parameters);
        }

        public async Task<string> GetAsync(string relativeUrl, IEnumerable<KeyValuePair<string, string>> parameters,
            CancellationToken cancellationToken = new CancellationToken())
        {
            ReplaceUrl();
            return await _connection.GetAsync(relativeUrl, parameters, cancellationToken);
        }               
    }

Instantiation of ISolrService is scoped and this fits well with usual requests in Controllers. BTW: This solution will not work in background tasks where there is no valid _httpContextAccessor.HttpContext.

@mausch
Copy link
Member

mausch commented Jun 11, 2023

Yes, pulling the tenant ID from some injected service is the way to go.
You could even write an ITenantProvider (just making up a name, not an interface that's part of SolrNet) to abstract away the HttpContext and be able to use it in non-http contexts.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants