Sitecore 7 Lucene.Net.Contrib highlight search results - c#

I am trying to do highlighting on the search results. Here is the relevant part of my code.
QueryScorer scorer = new QueryScorer(q);
Lucene.Net.Search.Highlight.IFormatter formatter = new SimpleHTMLFormatter("<b>", "</b>");
Lucene.Net.Search.Highlight.Highlighter highlighter = new Highlighter(formatter, scorer);
highlighter.TextFragmenter = new SimpleFragmenter(800);
Lucene.Net.Util.Version vers = new Lucene.Net.Util.Version();
vers = Lucene.Net.Util.Version.LUCENE_30;
TokenStream stream = new StandardAnalyzer(vers).TokenStream(string.Empty, new StringReader(text));
string s = string.Empty;
try
{
s = highlighter.GetBestFragments(stream, text, 10, "...");
}
Here, GetBestFragments method throws a System.MissingMethodException.
I have tried to replace the original Lucene.net dll with Lucene.Net.Contrib but this time, I dont know what I should write instead of TokenStream. It doesnt exist in Lucene.Net.Contrib.* dlls.
I am working on existing code and I need to find out how I can rewrite TokenStream class and GetBestFragments method.
Thanx

The problem was something about deployment, that the new compatible Lucene.dll was replaced by the incompatible Sitecore7 dll.
So, if both lucene.net and lucene.net.contrib dll are referenced, it should work.
Not directly the solution to my question, but this source is worth mentioning again. (About lucene.dll versions) : http://laubplusco.net/sitecore-7-lucen-3-0-highlighted-results/

Related

Fetching more than 1000 rows from Domino LDAP server using .NET Core 5 and Novell.Directory.Ldap.NETStandard

I want to fetch all the users from a large location of our Domino LDAP, around ~2000 users altogether. Since .NET Core sadly doesn't have a platform independent LDAP library, I'm using Novell.Directory.Ldap.NETStandard with this POC:
var cn = new Novell.Directory.Ldap.LdapConnection();
cn.Connect("dc.internal", 389);
cn.Bind("user", "pw");
string filter = "location=MyLoc";
var result = cn.Search("", Novell.Directory.Ldap.LdapConnection.ScopeOne, filter, new string[] { Novell.Directory.Ldap.LdapConnection.AllUserAttrs }, typesOnly: false);
int count = 0;
while (result.HasMore()) {
var entry = result.Next();
count++;
Console.WriteLine(entry.Dn);
}
It prints me a lot of entries, but not all. When count = 1000 I got an Size Limit Exceeded exception. I guess this is because I need to use some kind of pagination, so not all entries woult be returned in a single request. There are different questions like this or this one. Both in Java, the .NET Core API seems somehow different.
Approach 1: Try to find out how LdapSearchRequest works in .NET Core
byte[] resumeCookie = null;
LdapMessageQueue queue = null;
var searchReq = new LdapSearchRequest("", LdapConnection.ScopeOne, filter, new string[] { LdapConnection.AllUserAttrs },
LdapSearchConstraints.DerefNever, maxResults: 3000, serverTimeLimit: 0, typesOnly: false, new LdapControl[] { new SimplePagedResultsControl(size: 100, resumeCookie) });
var searchRequest = cn.SendRequest(searchReq, queue);
I'm trying to figure out how the Java examples can be used in .NET Core. This looks good, however I can't figure out how to fetch the LDAP entries. I only get an message id. By looking into the source it seems that I'm on the right way, but they're using MessageAgent which cannot be used outside since it's internal sealed. This is propably the reason why searching for LdapRearchRequest in the source code doesn't give many results.
Approach 2: Using SimplePagedResultsControlHandler
var opts = new SearchOptions("", LdapConnection.ScopeOne, filter, new string[] { LdapConnection.AllUserAttrs });
// For testing purpose: https://github.com/dsbenghe/Novell.Directory.Ldap.NETStandard/issues/163
cn.SearchConstraints.ReferralFollowing = false;
var pageControlHandler = new SimplePagedResultsControlHandler(cn);
var rows = pageControlHandler.SearchWithSimplePaging(opts, pageSize: 100);
This throws a Unavaliable Cricital Extension exception. First I thought that this is an issue of the .NET port, which may doesn't support all the features of the original Java library yet. It seems complete and according to further researches, it looks like to be an LDAP error code. So this must be something which has to be supported by the server, but is not supported by Domino.
I couldn't make at least one of those approachs work, but found another way: Cross platform support for the System.DirectoryServices.Protocols namespace was was added in .NET 5. This was missing for a long time in .NET Core and I guess this is the main reason why libraries like Novell.Directory.Ldap.NETStandard were ported to .NET Core - in times of .NET Core 1.x this was the only way I found to authenticate against LDAP wich works on Linux too.
After having a deeper look into System.DirectoryServices.Protocols, it works well out of the box, even for ~2k users. My basic POC class looks like this:
public class DominoLdapManager {
LdapConnection cn = null;
public DominoLdapManager(string ldapHost, int ldapPort, string ldapBindUser, string ldapBindPassword) {
var server = new LdapDirectoryIdentifier(ldapHost, ldapPort);
var credentials = new NetworkCredential(ldapBindUser, ldapBindPassword);
cn = new LdapConnection(server);
cn.AuthType = AuthType.Basic;
cn.Bind(credentials);
}
public IEnumerable<DominoUser> Search(string filter, string searchBase = "") {
string[] attributes = { "cn", "mail", "companyname", "location" };
var req = new SearchRequest(searchBase, filter, SearchScope.Subtree, attributes);
var resp = (SearchResponse)cn.SendRequest(req);
foreach (SearchResultEntry entry in resp.Entries) {
var user = new DominoUser() {
Name = GetStringAttribute(entry, "cn"),
Mail = GetStringAttribute(entry, "mail"),
Company = GetStringAttribute(entry, "companyname"),
Location = GetStringAttribute(entry, "location")
};
yield return user;
}
yield break;
}
string GetStringAttribute(SearchResultEntry entry, string key) {
if (!entry.Attributes.Contains(key)) {
return string.Empty;
}
string[] rawVal = (string[])entry.Attributes[key].GetValues(typeof(string));
return rawVal[0];
}
}
Example usage:
var ldapManager = new DominoLdapManager("ldap.host", 389, "binduser", "pw");
var users = ldapManager.Search("objectClass=person");
But it's not solved with Novell.Directory.Ldap.NETStandard as the title said
This doesn't solve my problem with the Novell.Directory.Ldap.NETStandard library as the title suggested, yes. But since System.DirectoryServices.Protocols is a official .NET package maintained by Microsoft and the .NET foundation, this seems the better aproach for me. The foundation will take care to keep it maintained and compatible with further .NET releases. When I wrote the question, I was not aware of the fact that Linux support is added now.
Don't get me wrong, I don't want to say that third packages are bad by design - that would be completely wrong. However, when I have the choice between a official package and a third party one, I think it makes sense to prefer the official one. Except there would be a good reason against that - which is not the case here: The official package (which doesn't exist in the past) works better to solve this issue than the third party one.

How to handle VSTS credentials in a VSIX extension

I have a Visual Studio extension that we use internally for a project and one of the things it needs to be able to do is post tickets to VSTS. Previously we were using onsite TFS and making a connection to post tickets was as simple as:
var vssCreds = new VssCredentials(true);
projectCollection = new TfsTeamProjectCollection(url, vssCreds);
workItems = projectCollection.GetService<WorkItemStore>();
project = workItems.Projects["My Project"];
defaultType = project.WorkItemTypes["Bug"];
//...
var newItem = new WorkItem(defaultType)
{
Title = title
};
newItem.Fields["Assigned To"].Value = assignTo;
newItem.Fields["Repro Steps"].Value = repoSteps;
var validationResult = newItem.Validate();
newItem.Save();
And this worked fine. But after upgrading to VSTS I'm having a hard time getting the credentials part to work. I changed this line:
projectCollection = new TfsTeamProjectCollection(url, vssCreds);
To this:
projectCollection = new TfsTeamProjectCollection(url, new VssClientCredentials());
And this worked fine for me. But when I share it with other people on my team it didn't work at first and then started working a little later. I am guessing that interacting with VSTS caused their credentials to be loaded so that it then worked. But I have at least one person who just seems to be completely unable to make it work.
So what's the correct way to get it to use the VSTS credentials (that should already exist in VS)?
I see this overload for VssClientCredentials (https://msdn.microsoft.com/en-us/library/dn228355(v=vs.120).aspx):
public VssClientCredentials(
IVssCredentialPrompt credentialPrompt
)
Which I suspect might be useful, but I can't seem to find out if there's a built in implementation of IVssCredentialPrompt somewhere or, if not, how to implement it.
Remove the related key from
Computer\HKEY_CURRENT_USER\Software\Microsoft\VSCommon\14.0\ClientServices\TokenStorage\VisualStudio\VssApp, then authentication again.
You also can specify other Kind (vssApp by default) and Namespace (VisualStudio by default) by using this code:
var c = new VssClientCredentials();
c.Storage = new VssClientCredentialStorage(storageKind: "VssApp2", storageNamespace: "VisualStudio");
projectCollection = new TfsTeamProjectCollection(url, c);
For reasons that are entirely unclear and from this answer to an entirely different question: https://stackoverflow.com/a/40256731/1250301
It seems that spawning a new thread causes a login prompt to appear if needed and fix all the problems. So if I do this:
Task.Run(() =>
{
var url = new Uri(_tfsUrl);
var cred = new VssClientCredentials();
projectCollection = new TfsTeamProjectCollection(url, cred);
workItems = projectCollection.GetService<WorkItemStore>();
}).Wait();
project = workItems.Projects["Job Posting Data"];
defaultType = project.WorkItemTypes["Bug"];
taskType = project.WorkItemTypes["Task"];
Then it works. I have no idea why it works, or why this is necessary (at first I thought maybe it was a problem with not being in the UI thread so I'd tried Application.Current.Dispatcher.Invoke which did not work) but it seems to have fixed the problem.

ArcObject IWMSServiceDescription.get_LayerDescription C# error

My goal is to connect to a WMS Service and display a layer on my application's map using ESRI's ArcObject API for .NET.
Here is the part of my code I am struggling with:
...
String url = "some value";
String layerTitle = "another value";
...
PropertySet props = new PropertySet();
props.SetProperty("URL", url);
WMSConnectionName connectionName = new WMSConnectionName();
connectionName.ConnectionProperties = props;
WMSMapLayer mapLayer = new WMSMapLayer();
(mapLayer as IDataLayer).Connect(connectionName as IName);
IWMSGroupLayer groupLayer = (IWMSGroupLayer)mapLayer;
IWMSServiceDescription serviceDescription = groupLayer.WMSServiceDescription;
IWMSLayerDescription layerDescription = serviceDescription.get_LayerDescription(0);
groupLayer.CreateWMSLayer(layerDescription);
groupLayer.get_Layer(0).Visible = true;
ILayer layer = (ILayer)groupLayer;
layer.Name = "WxOverlays " + layerTitle;
layer.Visible = true;
At run time I encounter:
System.Runtime.InteropServices.COMException (0x8000FFFF): The supplied
command does not exist in the command pool at
ESRI.ArcGIS.GISClient.IWMSServiceDescription.get_LayerDescription(Int32
index)
A google search revealed that some similar methods in the ArcObject API throw the same exception because they are not supported in C#. Has anyone encountered this before? Anyone see a way around it? Unfortunately, I am stuck using C#, so using Java or something that may have better support from ESRI is out of the question.

How to load quickdic dictionary into C#

I have downloaded a dictionary file from http://code.google.com/p/quickdic-dictionary/
But the file extension is .quickdic and is not plain text.
How can I load the quickdic dictionaries (.quickdic) into c# to make simple word queries?
I browsed through the git code, and a few things stuck out.
First, in the DictionaryActivity.java file, there is the following in onCreate():
final String name = application.getDictionaryName(dictFile.getName());
this.setTitle("QuickDic: " + name);
dictRaf = new RandomAccessFile(dictFile, "r");
dictionary = new Dictionary(dictRaf);
That Dictionary Class is not the built in class with Java, but is here according to the imports:
import com.hughes.android.dictionary.engine.Dictionary;
When I look there, it shows a constructor for a Dictionary taking a RandomAccessFile as the parameter. Here's that source code:
public Dictionary(final RandomAccessFile raf) throws IOException {
dictFileVersion = raf.readInt();
if (dictFileVersion < 0 || dictFileVersion > CURRENT_DICT_VERSION) {
throw new IOException("Invalid dictionary version: " + dictFileVersion);
}
creationMillis = raf.readLong();
dictInfo = raf.readUTF();
// Load the sources, then seek past them, because reading them later disrupts the offset.
try {
final RAFList<EntrySource> rafSources = RAFList.create(raf, new EntrySource.Serializer(this), raf.getFilePointer());
sources = new ArrayList<EntrySource>(rafSources);
raf.seek(rafSources.getEndOffset());
pairEntries = CachingList.create(RAFList.create(raf, new PairEntry.Serializer(this), raf.getFilePointer()), CACHE_SIZE);
textEntries = CachingList.create(RAFList.create(raf, new TextEntry.Serializer(this), raf.getFilePointer()), CACHE_SIZE);
if (dictFileVersion >= 5) {
htmlEntries = CachingList.create(RAFList.create(raf, new HtmlEntry.Serializer(this), raf.getFilePointer()), CACHE_SIZE);
} else {
htmlEntries = Collections.emptyList();
}
indices = CachingList.createFullyCached(RAFList.create(raf, indexSerializer, raf.getFilePointer()));
} catch (RuntimeException e) {
final IOException ioe = new IOException("RuntimeException loading dictionary");
ioe.initCause(e);
throw ioe;
}
final String end = raf.readUTF();
if (!end.equals(END_OF_DICTIONARY)) {
throw new IOException("Dictionary seems corrupt: " + end);
}
So, anyway, this is how his java code reads the file in.
Hopefully, this helps you simulate this in C#.
From here you would probably want to see how he is serializing the EntrySource, PairEntry, TextEntry, and HtmlEntry, as well as the indexSerializer.
Next look to see how RAFList.create() works.
Then see how that result is incorporated in creating a CachingList using CachingList.create()
Disclaimer: I'm not sure if the built in serializer in C# uses the same format as Java's, so you may need to simulate that too :)

Delete Record from C# using ManagedInterop

I am working with a C# project that uses the dll, Microsoft.Dynamics.AX.ManagedInterop to work with an AX 2012 environment. From within the code, I need to find a SalesQuotationLine based on specific criteria and delete it. So far, I can get the record I need but I am unable to delete it because I am not using a TTSBEGIN/TTSCOMMIT statement and I am not using FORUPDATE. This is my code:
DictTable dictTable = new DictTable(Global.tableName2Id("SalesQuotationLine"));
int quotationIdFieldId = (int)dictTable.Call("fieldName2Id", "QuotationId");
int bdcParentRecIdFieldId = (int)dictTable.Call("fieldName2Id", "BDCParentRecId");
var query = new Query();
var datasource = query.addDataSource(Global.tableName2Id("SalesQuotationLine"));
var queryRange1 = datasource.addRange(quotationIdFieldId);
queryRange1.value = "=" + line.QuotationId;
QueryRun queryRun = new QueryRun(query as object);
while (queryRun.next())
{
var result = queryRun.get(Global.tableName2Id("SalesQuotationLine"));
result.Delete();
}
I also looked at the code here, http://msdn.microsoft.com/en-us/library/cc197113.aspx but I found that I cannot use it since the code I am working with does not use the .NET Business Connector (I am not sure when one dll should be used over the other).
Use TTSBegin() and TTSCommit() methods on Microsoft.Dynamics.AX.ManagedInterop.RuntimeContext.Current. The forUpdate flag can be set by QueryBuildDataSource's update().
It may be easier (and better for maintenance) to write it in an X++ method just call the method from C#.

Categories