Skip to content

Commit 7d9c4e7

Browse files
authored
Merge pull request #1 from slorello89/dotnet-redistack-tutorial
.NET Stack/OM tutorial
2 parents a5490a1 + 1727ab7 commit 7d9c4e7

File tree

5 files changed

+334
-10
lines changed

5 files changed

+334
-10
lines changed
117 KB
Loading
2.62 KB
Loading
18.4 KB
Loading

docs/stack/get-started/tutorials/stack-dot-net.md

Lines changed: 0 additions & 10 deletions
This file was deleted.
Lines changed: 334 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,334 @@
1+
---
2+
title: "Redis OM .NET"
3+
linkTitle: .NET
4+
description: Learn how to build with Redis Stack and .NET
5+
weight: 1
6+
---
7+
8+
[Redis OM .NET](https://github.com/redis/redis-om-dotnet) is a purpose-built library for handling documents in Redis Stack. In this tutorial, we'll build a simple ASP.NET Core Web-API app for performing CRUD operations on a simple Person & Address model, and we'll accomplish all of this with Redis OM .NET.
9+
10+
## Prerequisites
11+
12+
* [.NET 6 SDK](https://dotnet.microsoft.com/en-us/download/dotnet/6.0)
13+
* And IDE for writing .NET (Visual Studio, Rider, Visual Studio Code)
14+
* Optional: Docker Desktop for running redis-stack in docker for local testing.
15+
16+
## Skip to the code
17+
18+
If you want to skip this tutorial and just jump straight into code, all the source code is available in [GitHub](https://github.com/redis-developer/redis-om-dotnet-skeleton-app)
19+
20+
## Run Redis Stack
21+
22+
There are a variety of ways to run Redis Stack. One way is to use the docker image:
23+
24+
```
25+
docker run -d -p 6379:6379 -p 8001:8001 redislabs/redis-stack
26+
```
27+
28+
## Create the project
29+
30+
To create the project, just run:
31+
32+
```bash
33+
dotnet new webapi -n Redis.OM.Skeleton --no-https --kestrelHttpPort 5000
34+
```
35+
36+
Then open the `Redis.OM.Skeleton.csproj` file in your IDE of choice.
37+
38+
## Configure the app
39+
40+
Add a `"REDIS_CONNECTION_STRING" field to your `appsettings.json` file to configure the application. Set that connection string to be the URI of your Redis instance. If using the docker command mentioned earlier, your connection string will be `redis://localhost:6379`.
41+
42+
## Create the model
43+
44+
Now it's time to create the `Person`/`Address` model that the app will use for storing/retrieving people. Create a new directory called `Model` and add the files `Address.cs` and `Person.cs` to it. In `Address.cs`, add the following:
45+
46+
```csharp
47+
using Redis.OM.Modeling;
48+
49+
namespace Redis.OM.Skeleton.Model;
50+
51+
public class Address
52+
{
53+
[Indexed]
54+
public int? StreetNumber { get; set; }
55+
56+
[Indexed]
57+
public string? Unit { get; set; }
58+
59+
[Searchable]
60+
public string? StreetName { get; set; }
61+
62+
[Indexed]
63+
public string? City { get; set; }
64+
65+
[Indexed]
66+
public string? State { get; set; }
67+
68+
[Indexed]
69+
public string? PostalCode { get; set; }
70+
71+
[Indexed]
72+
public string? Country { get; set; }
73+
74+
[Indexed]
75+
public GeoLoc Location { get; set; }
76+
}
77+
```
78+
79+
Here, you'll notice that except `StreetName`, marked as `Searchable`, all the fields are decorated with the `Indexed` attribute. These attributes (`Searchable` and `Indexed`) tell Redis OM that you want to be able to use those fields in queries when querying your documents in Redis Stack. `Address` will not be a Document itself, so the top-level class is not decorated with anything; instead, the `Address` model will be embedded in our `Person` model.
80+
81+
To that end, add the following to `Person.cs`
82+
83+
```csharp
84+
using Redis.OM.Modeling;
85+
86+
namespace Redis.OM.Skeleton.Model;
87+
88+
[Document(StorageType = StorageType.Json, Prefixes = new []{"Person"})]
89+
public class Person
90+
{
91+
[RedisIdField] [Indexed]public string? Id { get; set; }
92+
93+
[Indexed] public string? FirstName { get; set; }
94+
95+
[Indexed] public string? LastName { get; set; }
96+
97+
[Indexed] public int Age { get; set; }
98+
99+
[Searchable] public string? PersonalStatement { get; set; }
100+
101+
[Indexed] public string[] Skills { get; set; } = Array.Empty<string>();
102+
103+
[Indexed(CascadeDepth = 1)] Address? Address { get; set; }
104+
105+
}
106+
```
107+
108+
There are a few things to take note of here:
109+
110+
1. `[Document(StorageType = StorageType.Json, Prefixes = new []{"Person"})]` Indicates that the data type that Redis OM will use to store the document in Redis is JSON and that the prefix for the keys for the Person class will be `Person`.
111+
112+
2. `[Indexed(CascadeDepth = 1)] Address? Address { get; set; }` is one of two ways you can index an embedded object with Redis OM. This way instructs the index to cascade to the objects in the object graph, `CascadeDepth` of 1 means that it will traverse just one level, indexing the object as if it were building the index from scratch. The other method uses the `JsonPath` property of the individual indexed fields you want to search for. This more surgical approach limits the size of the index.
113+
114+
3. the `Id` property is marked as a `RedisIdField`. This denotes the field as one that will be used to generate the document's key name when it's stored in Redis.
115+
116+
## Create the Index
117+
118+
With the model built, the next step is to create the index in Redis. The most correct way to manage this is to spin the index creation out into a Hosted Service, which will run which the app spins up. Create a' HostedServices' directory and add `IndexCreationService.cs` to that. In that file, add the following, which will create the index on startup.
119+
120+
```csharp
121+
using Redis.OM.Skeleton.Model;
122+
123+
namespace Redis.OM.Skeleton.HostedServices;
124+
125+
public class IndexCreationService : IHostedService
126+
{
127+
private readonly RedisConnectionProvider _provider;
128+
public IndexCreationService(RedisConnectionProvider provider)
129+
{
130+
_provider = provider;
131+
}
132+
133+
public async Task StartAsync(CancellationToken cancellationToken)
134+
{
135+
await _provider.Connection.CreateIndexAsync(typeof(Person));
136+
}
137+
138+
public Task StopAsync(CancellationToken cancellationToken)
139+
{
140+
return Task.CompletedTask;
141+
}
142+
}
143+
```
144+
145+
## Inject the RedisConnectionProvider
146+
147+
Redis OM uses the `RedisConnectionProvider` class to handle connections to Redis and provides the classes you can use to interact with Redis. To use it, simply inject an instance of the RedisConnectionProvider into your app. In your `Program.cs` file, add:
148+
149+
```csharp
150+
builder.Services.AddSingleton(new RedisConnectionProvider(builder.Configuration["REDIS_CONNECTION_STRING"]));
151+
```
152+
153+
This will pull your connection string out of the config and initialize the provider. The provider will now be available in your controllers/services to use.
154+
155+
## Create the PeopleController
156+
157+
The final puzzle piece is to write the actual API controller for our People API. In the `controllers` directory, add the file `PeopleController.cs`, the skeleton of the `PeopleController`class will be:
158+
159+
```csharp
160+
using Microsoft.AspNetCore.Mvc;
161+
using Redis.OM.Searching;
162+
using Redis.OM.Skeleton.Model;
163+
164+
namespace Redis.OM.Skeleton.Controllers;
165+
166+
[ApiController]
167+
[Route("[controller]")]
168+
public class PeopleController : ControllerBase
169+
{
170+
171+
}
172+
```
173+
174+
### Inject the RedisConnectionProvider
175+
176+
To interact with Redis, inject the RedisConnectionProvider. During this dependency injection, pull out a `RedisCollection<Person>` instance, which will allow a fluent interface for querying documents in Redis.
177+
178+
```csharp
179+
private readonly RedisCollection<Person> _people;
180+
private readonly RedisConnectionProvider _provider;
181+
public PeopleController(RedisConnectionProvider provider)
182+
{
183+
_provider = provider;
184+
_people = (RedisCollection<Person>)provider.RedisCollection<Person>();
185+
}
186+
```
187+
188+
### Add route for creating a Person
189+
190+
The first route to add to the API is a POST request for creating a person, using the `RedisCollection`, it's as simple as calling `InsertAsync`, passing in the person object:
191+
192+
193+
```csharp
194+
[HttpPost]
195+
public async Task<Person> AddPerson([FromBody] Person person)
196+
{
197+
await _people.InsertAsync(person);
198+
return person;
199+
}
200+
```
201+
202+
### Add route to filter by age
203+
204+
The first filter route to add to the API will let the user filter by a minimum and maximum age. Using the LINQ interface available to the `RedisCollection`, this is a simple operation:
205+
206+
```csharp
207+
[HttpGet("filterAge")]
208+
public IList<Person> FilterByAge([FromQuery] int minAge, [FromQuery] int maxAge)
209+
{
210+
return _people.Where(x => x.Age >= minAge && x.Age <= maxAge).ToList();
211+
}
212+
```
213+
214+
### Filter by GeoLocation
215+
216+
Redis OM has a `GeoLoc` data structure, an instance of which is indexed by the `Address` model, with the `RedisCollection`, it's possible to find all objects with a radius of particular position using the `GeoFilter` method along with the field you want to filter:
217+
218+
219+
```csharp
220+
[HttpGet("filterGeo")]
221+
public IList<Person> FilterByGeo([FromQuery] double lon, [FromQuery] double lat, [FromQuery] double radius, [FromQuery] string unit)
222+
{
223+
return _people.GeoFilter(x => x.Address!.Location, lon, lat, radius, Enum.Parse<GeoLocDistanceUnit>(unit)).ToList();
224+
}
225+
```
226+
227+
### Filter by exact string
228+
229+
When a string property in your model is marked as `Indexed`, e.g. `FirstName` and `LastName`, Redis OM can perform exact text matches against them. For example, the following two routes filter by `PostalCode` and name demonstrate exact string matches.
230+
231+
```csharp
232+
[HttpGet("filterName")]
233+
public IList<Person> FilterByName([FromQuery] string firstName, [FromQuery] string lastName)
234+
{
235+
return _people.Where(x => x.FirstName == firstName && x.LastName == lastName).ToList();
236+
}
237+
238+
[HttpGet("postalCode")]
239+
public IList<Person> FilterByPostalCode([FromQuery] string postalCode)
240+
{
241+
return _people.Where(x => x.Address!.PostalCode == postalCode).ToList();
242+
}
243+
```
244+
245+
### Filter with a full-text search
246+
247+
When a property in the model is marked as `Searchable`, like `StreetAddress` and `PersonalStatement`, you can perform a full-text search, see the filters for the `PersonalStatement` and `StreetAddress`:
248+
249+
250+
```csharp
251+
[HttpGet("fullText")]
252+
public IList<Person> FilterByPersonalStatement([FromQuery] string text){
253+
return _people.Where(x => x.PersonalStatement == text).ToList();
254+
}
255+
256+
[HttpGet("streetName")]
257+
public IList<Person> FilterByStreetName([FromQuery] string streetName)
258+
{
259+
return _people.Where(x => x.Address!.StreetName == streetName).ToList();
260+
}
261+
```
262+
263+
### Filter by array membership
264+
265+
When a string array or list is marked as `Indexed`, Redis OM can filter all the records containing a given string using the `Contains` method of the array or list. For example, our `Person` model has a list of skills you can query by adding the following route.
266+
267+
```csharp
268+
[HttpGet("skill")]
269+
public IList<Person> FilterBySkill([FromQuery] string skill)
270+
{
271+
return _people.Where(x => x.Skills.Contains(skill)).ToList();
272+
}
273+
```
274+
275+
### Updating a person
276+
277+
Updating a document in Redis Stack with Redis OM can be done by first materializing the person object, making your desired changes, and then calling `Save` on the collection. The collection is responsible for keeping track of updates made to entities materialized in it; therefore, it will track and apply any updates you make in it. For example, add the following route to update the age of a Person given their Id:
278+
279+
280+
```csharp
281+
[HttpPatch("updateAge/{id}")]
282+
public IActionResult UpdateAge([FromRoute] string id, [FromBody] int newAge)
283+
{
284+
foreach (var person in _people.Where(x => x.Id == id))
285+
{
286+
person.Age = newAge;
287+
}
288+
_people.Save();
289+
return Accepted();
290+
}
291+
```
292+
293+
### Delete a person
294+
295+
Deleting a document from Redis can be done with `Unlink`. All that's needed is to call Unlink, passing in the key name. Given an id, we can reconstruct the key name using the prefix and the id:
296+
297+
298+
```csharp
299+
[HttpDelete("{id}")]
300+
public IActionResult DeletePerson([FromRoute] string id)
301+
{
302+
_provider.Connection.Unlink($"Person:{id}");
303+
return NoContent();
304+
}
305+
```
306+
307+
## Run the app
308+
309+
All that's left to do now is to run the app and test it. You can do so by running `dotnet run`, the app is now exposed on port 5000, and there should be a swagger UI that you can use to play with the API at http://localhost:5000/swagger. There's a couple of scripts, along with some data files, to insert some people into Redis using the API in the [GitHub repo](https://github.com/redis-developer/redis-om-dotnet-skeleton-app/tree/main/data)
310+
311+
## Viewing data in with Redis Insight
312+
313+
You can either install the Redis Insight GUI or use the Redis Insight GUI running on http://localhost:8001/.
314+
315+
You can view the data by following these steps:
316+
317+
1. Accept the EULA
318+
319+
![Accept EULA](./images/Accept_EULA.png)
320+
321+
2. Click the Add Redis Database button
322+
323+
![Add Redis Database Button](./images/Add_Redis_Database_button.png)
324+
325+
3. Enter your hostname and port name for your redis server. If you are using the docker image, this is `localhost` and `6379` and give your database an alias
326+
327+
![Configure Redis Insight Database](./images/Configure_Redis_Insight_Database.png)
328+
329+
4. Click `Add Redis Database.`
330+
331+
## Resources
332+
333+
* The source code for this tutorial can be found in [GitHub](https://github.com/redis-developer/redis-om-dotnet-skeleton-app).
334+
* To learn more about Redis OM you can check out the the guide on [Redis Developer](https://developer.redis.com/develop/dotnet/redis-om-dotnet/connecting-to-redis)

0 commit comments

Comments
 (0)