Catatan
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba masuk atau mengubah direktori.
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba mengubah direktori.
Dalam pelajaran sebelumnya, Anda menambahkan pencarian ke aplikasi web statis. Pelajaran ini menyorot langkah-langkah penting yang membentuk integrasi. Jika Anda mencari contekan tentang cara mengintegrasikan pencarian ke aplikasi web Anda, artikel ini menjelaskan apa yang perlu Anda ketahui.
SDK Azure Azure.Search.Documents
Aplikasi Fungsi menggunakan Azure SDK untuk Azure AI Search:
- NuGet: Azure.Search.Documents
- Dokumentasi Referensi: Pustaka Klien
Aplikasi fungsi mengautentikasi melalui SDK ke Azure AI Search API berbasis cloud menggunakan nama sumber daya, kunci sumber daya, dan nama indeks Anda. Rahasia disimpan dalam pengaturan aplikasi web statis dan ditarik ke fungsi sebagai variabel lingkungan.
Mengonfigurasi rahasia di file local.settings.json
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "",
"FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated",
"SearchApiKey": "",
"SearchServiceName": "",
"SearchIndexName": "good-books"
},
"Host": {
"CORS": "*"
}
}
Fungsi Azure: Mencari katalog
Search API mengambil istilah pencarian dan mencari di seluruh dokumen dalam indeks pencarian, mengembalikan daftar kecocokan. Melalui SUGGEST API, string parsial dikirim ke mesin pencari sebagai jenis pengguna, menyarankan istilah pencarian seperti judul buku dan penulis di seluruh dokumen dalam indeks pencarian, dan mengembalikan daftar kecil kecocokan.
Fungsi Azure menarik informasi konfigurasi pencarian, dan memenuhi kueri.
Pemberi saran pencarian, sg, ditentukan dalam file skema yang digunakan selama pengunggahan massal.
using Azure;
using Azure.Core.Serialization;
using Azure.Search.Documents;
using Azure.Search.Documents.Models;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Http;
using Microsoft.Extensions.Logging;
using System.Net;
using System.Text.Json;
using System.Text.Json.Serialization;
using WebSearch.Models;
using SearchFilter = WebSearch.Models.SearchFilter;
namespace WebSearch.Function
{
public class Search
{
private static string searchApiKey = Environment.GetEnvironmentVariable("SearchApiKey", EnvironmentVariableTarget.Process);
private static string searchServiceName = Environment.GetEnvironmentVariable("SearchServiceName", EnvironmentVariableTarget.Process);
private static string searchIndexName = Environment.GetEnvironmentVariable("SearchIndexName", EnvironmentVariableTarget.Process) ?? "good-books";
private readonly ILogger<Lookup> _logger;
public Search(ILogger<Lookup> logger)
{
_logger = logger;
}
[Function("search")]
public async Task<HttpResponseData> RunAsync(
[HttpTrigger(AuthorizationLevel.Anonymous, "post")] HttpRequestData req,
FunctionContext executionContext)
{
string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
var data = JsonSerializer.Deserialize<RequestBodySearch>(requestBody);
// Azure AI Search
Uri serviceEndpoint = new($"https://{searchServiceName}.search.windows.net/");
SearchClient searchClient = new(
serviceEndpoint,
searchIndexName,
new AzureKeyCredential(searchApiKey)
);
SearchOptions options = new()
{
Size = data.Size,
Skip = data.Skip,
IncludeTotalCount = true,
Filter = CreateFilterExpression(data.Filters)
};
options.Facets.Add("authors");
options.Facets.Add("language_code");
SearchResults<SearchDocument> searchResults = searchClient.Search<SearchDocument>(data.SearchText, options);
var facetOutput = new Dictionary<string, IList<FacetValue>>();
foreach (var facetResult in searchResults.Facets)
{
facetOutput[facetResult.Key] = facetResult.Value
.Select(x => new FacetValue { value = x.Value.ToString(), count = x.Count })
.ToList();
}
// Data to return
var output = new SearchOutput
{
Count = searchResults.TotalCount,
Results = searchResults.GetResults().ToList(),
Facets = facetOutput
};
var response = req.CreateResponse(HttpStatusCode.Found);
// Serialize data
var serializer = new JsonObjectSerializer(
new JsonSerializerOptions(JsonSerializerDefaults.Web));
await response.WriteAsJsonAsync(output, serializer);
return response;
}
public static string CreateFilterExpression(List<SearchFilter> filters)
{
if (filters is null or { Count: <= 0 })
{
return null;
}
List<string> filterExpressions = new();
List<SearchFilter> authorFilters = filters.Where(f => f.field == "authors").ToList();
List<SearchFilter> languageFilters = filters.Where(f => f.field == "language_code").ToList();
List<string> authorFilterValues = authorFilters.Select(f => f.value).ToList();
if (authorFilterValues.Count > 0)
{
string filterStr = string.Join(",", authorFilterValues);
filterExpressions.Add($"{"authors"}/any(t: search.in(t, '{filterStr}', ','))");
}
List<string> languageFilterValues = languageFilters.Select(f => f.value).ToList();
foreach (var value in languageFilterValues)
{
filterExpressions.Add($"language_code eq '{value}'");
}
return string.Join(" and ", filterExpressions);
}
}
}
Klien: Mencari dari katalog
Panggil Fungsi Azure di klien React dengan kode berikut.
import React, { useEffect, useState, Suspense } from 'react';
import axios from '../../axios.js';
import CircularProgress from '@mui/material/CircularProgress';
import { useLocation, useNavigate } from "react-router-dom";
import Results from '../../components/Results/Results';
import Pager from '../../components/Pager/Pager';
import Facets from '../../components/Facets/Facets';
import SearchBar from '../../components/SearchBar/SearchBar';
import "./Search.css";
export default function Search() {
let location = useLocation();
const navigate = useNavigate();
const [ results, setResults ] = useState([]);
const [ resultCount, setResultCount ] = useState(0);
const [ currentPage, setCurrentPage ] = useState(1);
const [ q, setQ ] = useState(new URLSearchParams(location.search).get('q') ?? "*");
const [ top ] = useState(new URLSearchParams(location.search).get('top') ?? 8);
const [ skip, setSkip ] = useState(new URLSearchParams(location.search).get('skip') ?? 0);
const [ filters, setFilters ] = useState([]);
const [ facets, setFacets ] = useState({});
const [ isLoading, setIsLoading ] = useState(true);
let resultsPerPage = top;
useEffect(() => {
setIsLoading(true);
setSkip((currentPage-1) * top);
const body = {
q: q,
top: top,
skip: skip,
filters: filters
};
axios.post( '/api/search', body)
.then(response => {
console.log(JSON.stringify(response.data))
setResults(response.data.results);
setFacets(response.data.facets);
setResultCount(response.data.count);
setIsLoading(false);
} )
.catch(error => {
console.log(error);
setIsLoading(false);
});
}, [q, top, skip, filters, currentPage]);
// pushing the new search term to history when q is updated
// allows the back button to work as expected when coming back from the details page
useEffect(() => {
navigate('/search?q=' + q);
setCurrentPage(1);
setFilters([]);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [q]);
let postSearchHandler = (searchTerm) => {
//console.log(searchTerm);
setQ(searchTerm);
}
var body;
if (isLoading) {
body = (
<div className="col-md-9">
<CircularProgress />
</div>);
} else {
body = (
<div className="col-md-9">
<Results documents={results} top={top} skip={skip} count={resultCount} query={q}></Results>
<Pager className="pager-style" currentPage={currentPage} resultCount={resultCount} resultsPerPage={resultsPerPage} setCurrentPage={setCurrentPage}></Pager>
</div>
)
}
// filters should be applied across entire result set,
// not just within the current page
const updateFilterHandler = (newFilters) => {
// Reset paging
setSkip(0);
setCurrentPage(1);
// Set filters
setFilters(newFilters);
};
return (
<main className="main main--search container-fluid">
<div className="row">
<div className="search-bar-column col-md-3">
<div className="search-bar">
<SearchBar postSearchHandler={postSearchHandler} query={q}></SearchBar>
</div>
<Facets facets={facets} filters={filters} setFilters={updateFilterHandler}></Facets>
</div>
{body}
</div>
</main>
);
}
Klien: Saran dari katalog
API fungsi Suggest dipanggil di aplikasi React di \client\src\components\SearchBar\SearchBar.js sebagai bagian dari komponen Lengkapi Otomatis Antarmuka Pengguna Material. Komponen ini menggunakan teks input untuk mencari penulis dan buku yang cocok dengan teks input lalu menampilkan kemungkinan kecocokan tersebut pada item yang dapat dipilih di daftar drop-down.
import React, { useState, useEffect } from 'react';
import { TextField, Autocomplete, Button, Box } from '@mui/material';
import axios from '../../axios.js';
export default function SearchBar2({ postSearchHandler, query }) {
const [q, setQ] = useState(() => query || '');
const [suggestions, setSuggestions] = useState([]);
const search = (value) => {
console.log(`search: ${value}`);
postSearchHandler(value);
};
useEffect(() => {
console.log(`useEffect getSuggestions: ${q}`);
if (q) {
axios.post('/api/suggest', { q, top: 5, suggester: 'sg' })
.then(response => {
setSuggestions(response.data.suggestions.map(s => s.text));
}).catch (error =>{
console.log(error);
setSuggestions([]);
});
}}, [q]);
const onInputChangeHandler = (event, value) => {
console.log(`onInputChangeHandler: ${value}`);
setQ(value);
};
const onChangeHandler = (event, value) => {
console.log(`onChangeHandler: ${value}`);
setQ(value);
search(value);
};
const onEnterButton = (event) => {
console.log(`onEnterButton: ${q}`);
// if enter key is pressed
if (event.key === 'Enter') {
search(q);
}
};
return (
<div
className="input-group"
style={{ width: '95%', display: 'flex', justifyContent: 'center', alignItems: 'center', margin: '0 auto' }}
>
<Box sx={{ display: 'flex', alignItems: 'center', width: '75%', minWidth: '390px' }}>
<Autocomplete
freeSolo
value={q}
options={suggestions}
onInputChange={onInputChangeHandler}
onChange={onChangeHandler}
disableClearable
sx={{
width: '75%',
'& .MuiAutocomplete-endAdornment': {
display: 'none'
}
}}
renderInput={(params) => (
<TextField
{...params}
id="search-box"
className="form-control rounded-0"
placeholder="What are you looking for?"
onBlur={() => setSuggestions([])}
onClick={() => setSuggestions([])}
/>
)}
/>
<div className="input-group-btn" style={{ marginLeft: '10px' }}>
<Button variant="contained" color="primary" onClick={() => {
console.log(`search button: ${q}`);
search(q)}
}>
Search
</Button>
</div>
</Box>
</div>
);
}
Fungsi Azure: Mendapatkan dokumen tertentu
API Pencarian Dokumen mengambil ID dan mengembalikan objek dokumen dari Indeks Pencarian.
using Azure;
using Azure.Core.Serialization;
using Azure.Search.Documents;
using Azure.Search.Documents.Models;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Http;
using Microsoft.Extensions.Logging;
using System.Net;
using System.Text.Json;
using WebSearch.Models;
namespace WebSearch.Function
{
public class Lookup
{
private static string searchApiKey = Environment.GetEnvironmentVariable("SearchApiKey", EnvironmentVariableTarget.Process);
private static string searchServiceName = Environment.GetEnvironmentVariable("SearchServiceName", EnvironmentVariableTarget.Process);
private static string searchIndexName = Environment.GetEnvironmentVariable("SearchIndexName", EnvironmentVariableTarget.Process) ?? "good-books";
private readonly ILogger<Lookup> _logger;
public Lookup(ILogger<Lookup> logger)
{
_logger = logger;
}
[Function("lookup")]
public async Task<HttpResponseData> RunAsync(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] HttpRequestData req,
FunctionContext executionContext)
{
// Get Document Id
var query = System.Web.HttpUtility.ParseQueryString(req.Url.Query);
string documentId = query["id"].ToString();
// Azure AI Search
Uri serviceEndpoint = new($"https://{searchServiceName}.search.windows.net/");
SearchClient searchClient = new(
serviceEndpoint,
searchIndexName,
new AzureKeyCredential(searchApiKey)
);
var getDocumentResponse = await searchClient.GetDocumentAsync<SearchDocument>(documentId);
// Data to return
var output = new LookupOutput
{
Document = getDocumentResponse.Value
};
var response = req.CreateResponse(HttpStatusCode.Found);
// Serialize data
var serializer = new JsonObjectSerializer(
new JsonSerializerOptions(JsonSerializerDefaults.Web));
await response.WriteAsJsonAsync(output, serializer);
return response;
}
}
}
Klien: Mendapatkan dokumen tertentu
API fungsi ini dipanggil di aplikasi React di \client\src\pages\Details\Detail.js sebagai bagian dari inisialisasi komponen:
import React, { useState, useEffect } from "react";
import { useParams } from 'react-router-dom';
import Rating from '@mui/material/Rating';
import CircularProgress from '@mui/material/CircularProgress';
import axios from '../../axios.js';
import "./Details.css";
export default function Details() {
let { id } = useParams();
const [document, setDocument] = useState({});
const [selectedTab, setTab] = useState(0);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
setIsLoading(true);
// console.log(id);
axios.get('/api/lookup?id=' + id)
.then(response => {
console.log(JSON.stringify(response.data))
const doc = response.data.document;
setDocument(doc);
setIsLoading(false);
})
.catch(error => {
console.log(error);
setIsLoading(false);
});
}, [id]);
// View default is loading with no active tab
let detailsBody = (<CircularProgress />),
resultStyle = "nav-link",
rawStyle = "nav-link";
if (!isLoading && document) {
// View result
if (selectedTab === 0) {
resultStyle += " active";
detailsBody = (
<div className="card-body">
<h5 className="card-title">{document.original_title}</h5>
<img className="image" src={document.image_url} alt="Book cover"></img>
<p className="card-text">{document.authors?.join('; ')} - {document.original_publication_year}</p>
<p className="card-text">ISBN {document.isbn}</p>
<Rating name="half-rating-read" value={parseInt(document.average_rating)} precision={0.1} readOnly></Rating>
<p className="card-text">{document.ratings_count} Ratings</p>
</div>
);
}
// View raw data
else {
rawStyle += " active";
detailsBody = (
<div className="card-body text-left">
<pre><code>
{JSON.stringify(document, null, 2)}
</code></pre>
</div>
);
}
}
return (
<main className="main main--details container fluid">
<div className="card text-center result-container">
<div className="card-header">
<ul className="nav nav-tabs card-header-tabs">
<li className="nav-item"><button className={resultStyle} onClick={() => setTab(0)}>Result</button></li>
<li className="nav-item"><button className={rawStyle} onClick={() => setTab(1)}>Raw Data</button></li>
</ul>
</div>
{detailsBody}
</div>
</main>
);
}
Model C# untuk mendukung aplikasi fungsi
Model berikut digunakan untuk mendukung fungsi dalam aplikasi ini.
using Azure.Search.Documents.Models;
using System.Text.Json.Serialization;
namespace WebSearch.Models
{
public class RequestBodyLookUp
{
[JsonPropertyName("id")]
public string Id { get; set; }
}
public class RequestBodySuggest
{
[JsonPropertyName("q")]
public string SearchText { get; set; }
[JsonPropertyName("top")]
public int Size { get; set; }
[JsonPropertyName("suggester")]
public string SuggesterName { get; set; }
}
public class RequestBodySearch
{
[JsonPropertyName("q")]
public string SearchText { get; set; }
[JsonPropertyName("skip")]
public int Skip { get; set; }
[JsonPropertyName("top")]
public int Size { get; set; }
[JsonPropertyName("filters")]
public List<SearchFilter> Filters { get; set; }
}
public class SearchFilter
{
public string field { get; set; }
public string value { get; set; }
}
public class FacetValue
{
public string value { get; set; }
public long? count { get; set; }
}
class SearchOutput
{
[JsonPropertyName("count")]
public long? Count { get; set; }
[JsonPropertyName("results")]
public List<SearchResult<SearchDocument>> Results { get; set; }
[JsonPropertyName("facets")]
public Dictionary<String, IList<FacetValue>> Facets { get; set; }
}
class LookupOutput
{
[JsonPropertyName("document")]
public SearchDocument Document { get; set; }
}
public class BookModel
{
public string id { get; set; }
public decimal? goodreads_book_id { get; set; }
public decimal? best_book_id { get; set; }
public decimal? work_id { get; set; }
public decimal? books_count { get; set; }
public string isbn { get; set; }
public string isbn13 { get; set; }
public string[] authors { get; set; }
public decimal? original_publication_year { get; set; }
public string original_title { get; set; }
public string title { get; set; }
public string language_code { get; set; }
public double? average_rating { get; set; }
public decimal? ratings_count { get; set; }
public decimal? work_ratings_count { get; set; }
public decimal? work_text_reviews_count { get; set; }
public decimal? ratings_1 { get; set; }
public decimal? ratings_2 { get; set; }
public decimal? ratings_3 { get; set; }
public decimal? ratings_4 { get; set; }
public decimal? ratings_5 { get; set; }
public string image_url { get; set; }
public string small_image_url { get; set; }
}
}
Langkah berikutnya
Untuk terus mempelajari selengkapnya tentang pengembangan Azure AI Search, coba tutorial berikutnya tentang pengindeksan: