Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 18
VisualStudioVersion = 18.3.11206.111 d18.3
VisualStudioVersion = 18.3.11206.111
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BlazorWebAssemblyXrefGenerator", "BlazorWebAssemblyXrefGenerator.csproj", "{AC54A61E-49B7-4494-B9FF-978F64A9EF7C}"
EndProject
Expand Down
106 changes: 82 additions & 24 deletions BlazorWebAssemblyXrefGenerator/Pages/Home.razor
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
@page "/"
@using System.Net
@using System.Text
@using System.Text.RegularExpressions
@inject IHttpClientFactory ClientFactory

Expand All @@ -10,17 +11,6 @@
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Search</h3>
<span style="float:right">
<InputSelect @bind-Value="dotNetVersion">
<option>aspnetcore-10.0</option>
<option>aspnetcore-9.0</option>
<option>aspnetcore-8.0</option>
<option>aspnetcore-7.0</option>
<option>aspnetcore-6.0</option>
<option>aspnetcore-5.0</option>
<option>aspnetcore-3.1</option>
</InputSelect>
</span>
</div>
<div class="panel-body">
<div class="form-group">
Expand Down Expand Up @@ -68,7 +58,6 @@
private SearchResults? SearchResultItems { get; set; }
private List<Result> ResultsToRender { get; set; } = new();
public string? message;
private string? dotNetVersion;

private const int MaxResults = 22;

Expand All @@ -83,6 +72,7 @@
message = string.Empty;

var apiClient = ClientFactory.CreateClient("APIClient");

if (string.IsNullOrEmpty(Model?.SearchText) || apiClient == null)
{
isSearching = false;
Expand All @@ -91,26 +81,32 @@

try
{
SearchResultItems = await apiClient.GetFromJsonAsync<SearchResults>($"api/apibrowser/dotnet/search?api-version=0.2&search={Model.SearchText}");
SearchResultItems = await apiClient.GetFromJsonAsync<SearchResults>($"api/apibrowser/dotnet/search?search={Model.SearchText}");

if (SearchResultItems?.Results?.Any() == true)
{
var index = 1;
foreach (var result in SearchResultItems.Results.Take(MaxResults))
{
var client = ClientFactory.CreateClient();
var encodedUrl = WebUtility.UrlEncode($"https://learn.microsoft.com/en-us{result.Url}?view={dotNetVersion}");
using var request = new HttpRequestMessage(HttpMethod.Get, $"https://corsproxy.io/?{encodedUrl}");
using var response = await client.SendAsync(request);
var content = await response.Content.ReadAsStringAsync();

var match = Regex.Match(content, "<meta name=\"ms.assetid\" content=\"(.+?)\" />");
if (match.Success)
if (!string.IsNullOrEmpty(result.DisplayName))
{
result.Link = match.Groups[1].Value.Replace("*", "%2A").Replace("`", "%60");

if (result.ItemType == "Constructor")
{
result.Link = Regex.Replace(result.DisplayName, @"\(.*\)", ".%23ctor%2A");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this produce the correct string for a constructor? Isn't there an extra name in the result? (copilot flagged this before in the PR)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think Copilot is incorrect. Consider ControllerBase's ctor. It shows up as ...

image

Therefore, I don't think it has an extra name in the result. Funny thing is that we have ZERO live ctors cross-linked in our almost 900 articles at this time, so I don't have a live example to check to know 100% for sure.

Correction: We do have live ctors ... my first filter didn't catch them. I've confirmed it: There are no double names in the links. For example ...

<xref:Microsoft.Extensions.Localization.ResourceManagerStringLocalizer.%23ctor%2A?displayProperty=nameWithType>

... at ...

https://learn.microsoft.com/en-us/aspnet/core/breaking-changes/5/localization-pubternal-apis-removed?view=aspnetcore-10.0#affected-apis

... correctly resolves to a cross-link that loads the API Browser to the ctor at ...

https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.localization.resourcemanagerstringlocalizer.-ctor?view=net-11.0-pp

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, thanks for the explanation.

}
else if (result.ItemType == "Method")
{
result.Link = Regex.Replace(result.DisplayName, @"\(.*\)", "%2A");
}
else
{
result.Link = result.DisplayName;
}

result.Link = ReplaceGenericArity(result.Link ?? string.Empty);
}

result.Index = index++;
result.Index = ResultsToRender.Count + 1;
ResultsToRender.Add(result);
Comment thread
Copilot marked this conversation as resolved.
StateHasChanged();
}
Expand All @@ -137,6 +133,68 @@
message = string.Empty;
}

private static string ReplaceGenericArity(string value)
{
var builder = new StringBuilder();

for (var index = 0; index < value.Length; index++)
{
if (value[index] != '<')
{
builder.Append(value[index]);
continue;
}

var depth = 1;
var endIndex = index + 1;

while (endIndex < value.Length && depth > 0)
{
if (value[endIndex] == '<')
{
depth++;
}
else if (value[endIndex] == '>')
{
depth--;
}

endIndex++;
}

if (depth > 0)
{
builder.Append(value[index]);
continue;
}

var genericArguments = value[(index + 1)..(endIndex - 1)];
var arity = 1;
var argumentDepth = 0;

foreach (var character in genericArguments)
{
if (character == '<')
{
argumentDepth++;
}
else if (character == '>')
{
argumentDepth--;
}
else if (character == ',' && argumentDepth == 0)
{
arity++;
}
}

builder.Append($"%60{arity}");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this work for generic methods as well? Isn't the convention there to use two backticks rather than one?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You might be right about it failing in that case. Copilot generated the method, and I'm just trying to quickly get the tool back online.

I'll check on how the API Browser expects the links for generic methods and get back to you.

index = endIndex - 1;
}

return builder.ToString();
}

public class FormModel
{
public string? SearchText { get; set; } = string.Empty;
Expand Down
Loading