Moviegraph is a demo and training application for ASP.NET Core and Neo4j.
wget http://dist.neo4j.org/neo4j-community-3.4.7-unix.tar.gz
tar xf neo4j-community-3.4.7-unix.tar.gz
cd neo4j-community-3.4.7
bin/neo4j-admin set-initial-password password
bin/neo4j start|console
Browse to
http://localhost:7474/
:play movies
call db.schema
(explore a bit)
git clone ...
export ASPNETCORE_ENVIRONMENT=Development
cd MovieGraph.Web && dotnet run
If you want to use a different language, feel free to convert the code.
http://127.0.0.1:5000
Answers/
- all the answers!!MovieGraph.sln
- the solution file that includes the projectMovieGraph.Web
MovieGraph.Web/
- the project itselfMovieGraph.Web/Controllers/
- the MVC controllersMovieGraph.Web/Views/
- the MVC views (razor)MovieGraph.Web/Model/
- the MVC modelsMovieGraph.Web/Helpers/
- couple of extension methods for Neo4jMovieGraph.Web/wwwroot/
- the static files (css)
The driver is added as a service - Startup.cs
// Add Neo4j Driver As A Singleton Service
services.AddSingleton<IDriver>(provider => GraphDatabase.Driver("bolt://localhost:7687", AuthTokens.Basic("neo4j", "password")));
MVC controllers accept IDriver
in their constructors, ASP.NET MVC passes our already registered singleton instance.
private readonly IDriver driver;
public HomeController(IDriver driver)
{
this.driver = driver;
}
You can have a look at the home page view templates at Views/Home
.
[Route("")]
public async Task<IActionResult> Index(string q)
{
return View("Index", await MatchMovies(q));
}
private async Task<IEnumerable<MovieModel>> MatchMovies(string term)
{
if (string.IsNullOrEmpty(term))
{
return Enumerable.Empty<MovieModel>();
}
var session = driver.Session();
try
{
return await session.ReadTransactionAsync(async tx =>
{
var cursor =
await tx.RunAsync(
"MATCH (movie:Movie) WHERE toLower(movie.title) " +
"CONTAINS toLower($term) RETURN movie",
new {term});
return await cursor.ToListAsync(record => new MovieModel(record));
});
}
finally
{
if (session != null)
{
await session.CloseAsync();
}
}
}
You can have a look at the home page view templates at Views/Movie
.
[Route("{id}")]
public async Task<IActionResult> Index(string id)
{
var movie = await MatchMovie(id);
if (movie == null)
{
return StatusCode(404);
}
return View("Index", movie);
}
private async Task<MovieModel> MatchMovie(string title)
{
var session = driver.Session();
try
{
return await session.ReadTransactionAsync(async tx =>
{
var cursor =
await tx.RunAsync(
"MATCH (movie:Movie) WHERE movie.title = $title " +
"OPTIONAL MATCH(person) -[:ACTED_IN]->(movie) " +
"RETURN movie, collect(person) AS actors",
new {title});
return new MovieModel(await cursor.SingleAsync());
});
}
finally
{
if (session != null)
{
await session.CloseAsync();
}
}
}
GetOrDefault
functions used are defined as extension methods by the static classes in Helpers
folder.
public class MovieModel
{
public const string MovieKey = "movie";
public const string TitleKey = "title";
public const string TaglineKey = "tagline";
public const string ReleasedKey = "released";
public const string ActorsKey = "actors";
public MovieModel(IRecord record)
{
var movie = record.GetOrDefault(MovieKey, (INode) null);
if (movie != null)
{
Title = movie.GetOrDefault<string>(TitleKey, null);
Tagline = movie.GetOrDefault<string>(TaglineKey, null);
Released = movie.GetOrDefault<int?>(ReleasedKey, null);
}
var actors = record.GetOrDefault(ActorsKey, (List<object>) null);
if (actors != null)
{
Actors = actors.Select(actor => new PersonModel((INode) actor)).OrderBy(p => p.Name);
}
}
public string Title { get; }
public string Tagline { get; }
public int? Released { get; }
public IEnumerable<PersonModel> Actors { get; }
}
GetOrDefault
functions used are defined as extension methods by the static classes in Helpers
folder.
public class PersonModel
{
public const string NameKey = "name";
public PersonModel(INode node)
{
if (node == null)
{
throw new ArgumentNullException(nameof(node));
}
Name = node.GetOrDefault<string>(NameKey, null);
}
public string Name { get; }
}
- Add links behind the movie cast list (movies are rendered by templates in
Views/Movie
) - Add a new
Person
controller (inControllers
) - Modify
PersonModel
to include year of birth and all films played in (inModel
) - Add a new view template for
Person
(save new view file(s) inViews/Person
)
cd Answers/1/MovieGraph.Web && dotnet run
- Add clickable star rating to each movie (movies are rendered by templates in
Views/Movie
) - Add handler for POST requests to
/movie/<title>
(inControllers/MovieController
) - Modify
MovieModel
to include stored star rating (inModel
)
cd Answers/2/MovieGraph.Web && dotnet run
- Add stars to search results (search results are rendered by templates in
Views/Home
) - Add order by box (search box is rendered in
Views/Shared/_Layout.cshtml
)
cd Answers/3/MovieGraph.Web && dotnet run