How to write a simple blog using Asp .Net MVC, Nhibernate, and Ninject. Part 1
- Tutorial
Transfer
Introduction
There are various ways to study technology: reading books, participating in conferences, creating examples, and others. I think one of the best ways is to create something useful for yourself using the technology being studied. A blog is one of the useful things you can easily do. In a series of articles, we will learn ASP.NET MVC, step by step, by creating a blog from scratch.
For simplicity, we will not build a commenting system; instead, we will use Disqus . Try to create a commenting system yourself; this will be a good exercise for you.
For application development, we will use ASP.NET MVC 4. I am not strong in the Entity Framework and we will use Fluent NHibernate / NHibernate to create a data access system (DAL). But if you like, you can use the Entity Framework. Finally, for dependency injection, we will use Ninject because of its simplicity.- In the first part, we will create the basic blog infrastructure. Let's create the necessary classes of models, data access components.
- In the second part, we implement controllers, action methods, and representations. At the end of the second part, we will have a working blog where we can see the latest posts, read the full text of the post, select posts by category or tag, and even search for interesting posts (posts).
- In the third part, we will create an admin console to manage posts, categories, and tags.
- In the fourth part, we will introduce the Disqus comment system in our blog. We will also introduce two new services: the AddThis exchange service and the Feedburner subscription service .
Technology
- ASP.NET MVC 4.0
- Fluent NHibernate and NHibernate
- SQL Server 2012
- Ninject
- jQuery
Creation of basic infrastructure.
We describe the tasks that we need to implement in this part:
Task 1. Display the last blog post.
Task 2. Display posts of a certain category,
Task 3. Display posts based on the tag. Task 4. Search for
posts.
Task 5. Display the entire post.
Task 6. Display posts a certain category in the widget
Task 7. Display posts based on the tag in the widget
Task 8. Display the last post in the widgetTask 1. Display the latest blog post.
To accomplish this task, we need to make a selection of blog posts from the database and display them in a view.
This task is large enough, so let's break it down into several smaller subtasks, which will be easier to perform separately- Create a basic solution
- Create Domain Classes
- Configure Fluent NHibernate and NHibernate
- Create mapping classes, data access classes, and methods
- Configure Ninject for JustBlog.Core
- Configure Ninject for MVC Project
- Create controller and actions
- Create view
4.1 Creating a basic solution
Create an MVC 4 web application project and name it JustBlog .
In the "Select a template" window, select "Empty template" .
Create a class library and name it JustBlog.Core . It is recommended to store domain classes and data access components in separate projects, so it will be easier for us to manage the application in terms of development, testing and deployment. Remember to add the link to JustBlog.Core to JustBlog .
The solution should look like the one below.4.2 Creating Domain Classes
In the JustBlog.Core project , create a new folder called Objects to place the domain classes in it. For our blog, we need to create three classes: Post , Category and Tag . Each Post belongs to one Category , but it can be marked with many Tags . Between Post and Category many-to-one relationships and between Post and Tag many-to-many relationships .
The relationship between the classes is shown in the screenshot below
. Our Post class is shownnamespace JustBlog.Core.Objects { public class Post { public virtual int Id { get; set; } public virtual string Title { get; set; } public virtual string ShortDescription { get; set; } public virtual string Description { get; set; } public virtual string Meta { get; set; } public virtual string UrlSlug { get; set; } public virtual bool Published { get; set; } public virtual DateTime PostedOn { get; set; } public virtual DateTime? Modified { get; set; } public virtual Category Category { get; set; } public virtual IList
Tags { get; set; } } }
Most properties are self-explanatory. The UrlSlug property is an alternative to the Title property and is used for addressing.Slug is a newspaper term. “Slug” is a short tag name that contains only letters, numbers, underscores or hyphens. Mostly used in URLs.
For example, we have a post entitled “Advanced Linq in C #”, published in August 2010, we create for it the URL http // localhost / archive / 2010/8 / Advanced Linq in C # . The post title may contain special characters (in this example, “#”) and not all servers can process requests containing special characters. Instead of using the Title property directly in the URL, we will use some alternative text that looks like the title of the post and we will take it from the Slug URL property .
In the above case, instead of using “Advanced Linq in C #” in the URL, we are going to use the friendly text (slug) “advanced_linq_in_csharp”so the address will be http // localhost / archive / 2010/8 / advanced_linq_in_csharp. In part 3, we will learn how to automatically create a slug from the post title.URL Slug
URL Slug is an SEO - and user-friendly part of the URL string for identifying, describing, and accessing a resource. Often an article’s page title is a suitable candidate for this.
The Meta property is used to store post description metadata and is used for SEO. All properties are marked as virtual, because NHibernate creates a proxy server at run time and it needs to have all properties of this class virtual to work.
The Category and Tag classes are very simple and are shown below. We use the UrlSlug property for the same reason that we discussed above.namespace JustBlog.Core.Objects { public class Category { public virtual int Id { get; set; } public virtual string Name { get; set; } public virtual string UrlSlug { get; set; } public virtual string Description { get; set; } public virtual IList
Posts { get; set; } } } namespace JustBlog.Core.Objects { public class Tag { public virtual int Id { get; set; } public virtual string Name { get; set; } public virtual string UrlSlug { get; set; } public virtual string Description { get; set; } public virtual IList
Posts { get; set; } } } 4.3 Configuring Fluent NHibernate and NHibernate
To access the database, we will use NHibernate along with Fluent NHibernate . NHibernate is an ORM tool like the Entity Framework, where relationships between classes and database tables are mapped through XML files. Fluent NHibernate is an extension for NHibernate that replaces mapping with XML files with mapping using special classes. Mapping through classes is much simpler than using XML files.
We can easily add links to NHibernate and Fluent NHibernate assemblies using the Nuget Package Manager Console . Open Package Manager from the Tools -> Library Package Manager -> Package Manager Console menu .
In the console, run the following command.PM> Install-Package FluentNHibernate
If the installation was successful, you will see the following assemblies in the References folder of the JustBlog.Core project .
FluentNHibernate
NHibernate
Iesi.Collections4.4 Creating mapping classes, data access classes, and methods
The next thing we need to do is create the necessary mapping classes. The mapping class is used to map the class and its properties to the table and its columns. Create a new folder in the JustBlog.Core project called Mappings to store all mapping classes.
To create a mapping class, we must inherit it from the generic ClassMap class of the Fluent NHibernate framework. All mappings must be performed in the constructor.
The mapping class for the Post class .using FluentNHibernate.Mapping; using JustBlog.Core.Objects; namespace JustBlog.Core.Mappings { public class PostMap: ClassMap
{ public PostMap() { Id(x => x.Id); Map(x => x.Title).Length(500).Not.Nullable(); Map(x => x.ShortDescription).Length(5000).Not.Nullable(); Map(x => x.Description).Length(5000).Not.Nullable(); Map(x => x.Meta).Length(1000).Not.Nullable(); Map(x => x.UrlSlug).Length(200).Not.Nullable(); Map(x => x.Published).Not.Nullable(); Map(x => x.PostedOn).Not.Nullable(); Map(x => x.Modified); References(x => x.Category).Column("Category").Not.Nullable(); HasManyToMany(x => x.Tags).Table("PostTagMap"); } } }
The extension method Id represents the name of the property to be set as the primary key for the table column.
The Map extension method is used to map the property to a table column. During property mapping, we can specify the size of the column, indicate whether it allows storing NULL values, or specify other specifications.
For instance:Map(x => x.ShortDescription).Length(5000).Not.Nullable();
If the generated column name is different from the property name, then we must pass the column name using the Column extension method .Map(x => x.Title).Column("post_title")
The References method is used to represent the many-to-one relationship between Post and Category using the external Category key column of the Post table . The HasManyToMany method is used to represent the many-to-many relationship between Post and Tag through a PostTagMap staging table . You can learn more about Fluent NHibernate and its extension methods from here .
By default, Fluent NHibernateassumes the table name is exactly the same as the class name, and the column names are exactly the same as the property names. If the table name is different, then we must map the table to the class using the Table extension method .
For instance:Table("tbl_posts");
The mapping classes for Category and Tag are exactly the same, except for their relationship with Post . Category is related to Post one-to-many , while Tag is related to Post many-to-many .namespace JustBlog.Core.Mappings { public class CategoryMap: ClassMap
{ public CategoryMap() { Id(x => x.Id); Map(x => x.Name).Length(50).Not.Nullable(); Map(x => x.UrlSlug).Length(50).Not.Nullable(); Map(x => x.Description).Length(200); HasMany(x => x.Posts).Inverse().Cascade.All().KeyColumn("Category"); } } }
The Inverse () extension method makes the other side of the relationship responsible for preservation.
The Cascade.All () extension method launches a cascading event call (down the hierarchy) in the collection entities (therefore, when a category is saved, posts will also be saved).namespace JustBlog.Core.Mappings { public class TagMap: ClassMap
{ public TagMap() { Id(x => x.Id); Map(x => x.Name).Length(50).Not.Nullable(); Map(x => x.UrlSlug).Length(50).Not.Nullable(); Map(x => x.Description).Length(200); HasManyToMany(x => x.Posts).Cascade.All().Inverse().Table("PostTagMap"); } } }
We will use the Repository template to access the database. We use this template to separate the data access code from the controllers, and this will help us simplify the unit testing of our controllers. The core of the repository template is an interface that contains descriptions of all data access methods.
In the JustBlog.Core project , create an IBlogRepository interface with two methods.using JustBlog.Core.Objects; namespace JustBlog.Core { public interface IBlogRepository { IList
Posts(int pageNo, int pageSize); int TotalPosts(); } }
The Posts method is used to return the last published paginated posts according to the values passed to it. The TotalPosts method returns the total number of published posts. Next, we will fill the interface with other methods.
In the JustBlog.Core project , a class called BlogRepository and implement the interface.using JustBlog.Core.Objects; using NHibernate; using NHibernate.Criterion; using NHibernate.Linq; using NHibernate.Transform; using System.Collections.Generic; using System.Linq; namespace JustBlog.Core { public class BlogRepository: IBlogRepository { // NHibernate object private readonly ISession _session; public BlogRepository(ISession session) { _session = session; } public IList
Posts(int pageNo, int pageSize) { var posts = _session.Query () .Where(p => p.Published) .OrderByDescending(p => p.PostedOn) .Skip(pageNo * pageSize) .Take(pageSize) .Fetch(p => p.Category) .ToList(); } var postIds = posts.Select(p => p.Id).ToList(); return _session.Query () .Where(p => postIds.Contains(p.Id)) .OrderByDescending(p => p.PostedOn) .FetchMany(p => p.Tags) .ToList(); } public int TotalPosts() { return _session.Query ().Where(p => p.Published).Count(); } } }
All database access should be through the NHibernate ISession object . When we read a collection of posts through ISession , the Category and Tags dependencies are not populated by default. The Fetch and FetchMany methods are used to tell NHibernate to populate them immediately.
In the Posts method , to get posts, we query the database twice because we cannot use FetchMany with the Skip and Take methodsin Linq request. So, first we select all the posts, then with their ID we select the posts again to get their tags. Please see this discussion thread for more information on this issue.NHibernate ISession
ISession is a persistence manager interface that is used to store and retrieve objects from a database.
Persistent means that this instance should be implemented in such a way that it is preserved after the completion of the process that generated it. Unlike transient, which means that this instance of the object is temporary - it ceases to exist after the completion of the process that generated it. (comment translator).4.5 Ninject settings for JustBlog.Core project
Dependency injection (DI) avoids instantiating specific dependency implementations within a class. These dependencies are usually injected into the class through the constructor, but sometimes through the properties. One of the main advantages of dependency injection is unit testing and we will see it when we write unit tests for controllers in Part 3.
There are many frameworks to simplify dependency injection, such as Castle Windsor, Unity, Autofac, StructureMap, Ninject, etc. We have chosen Ninject because it is easy to use.
Install Ninject in the JustBlog.Core project by running the following commands in the Package Manager Console .PM> Install-Package Ninject
PM> Install-Package Ninject.Web.Common
After successfully executing the commands, we will see that the Ninject and NinjectWeb.Common assemblies have been added to the project. Together with assemblies also add file NinjectWebCommon.cs a folder App_Start . In the near future I will explain why we need to install the Ninject.Web.Common extension.
We can configure Ninject in two ways, either using Global.asax.cs or through the App_Start file . We will use the first approach, so please delete the file NinjectWebCommon.cs folder App_Start , as well as delete unneeded assembly references WebActivator and Microsoft.Web.Infrastructure of the project.
The main functionality of any DI framework is the binding of interfaces to specific implementations of these interfaces. Associating an interface with a specific implementation is called binding. We can group a set of bindings associated with a particular Ninject module using Ninject Modules. All bindings and modules are loaded into the main Ninject component of the Kernel kernel. Whenever an application needs an instance of a particular class that implements the interface, Kernel provides it to the application.Ninject Module
The Ninject module is used to group matches / bindings related to a specific module in one class.
The BlogRepository class has a dependency with Nibernate ISession . To create an instance of ISession, we need another Nibernate interface called ISessionFactory . Let's create in the JustBlog.Core project a ninject module of a class called RepositoryModule that will connect both of these interfaces.
Created q The Ninject module must inherit from the abstract NinjectModule class and implement the Load method . In the Load method, we will bind both interfaces to methods using the Bind method .
The Bind method is used to bind an interface to a class that implements it.
For instance:Bind
().To ();
We can also map the interface to a method that creates and returns an implementation of the interface.Bind
().ToMethod(c => { var foo = new Foo(); return foo; }); NHibernate ISessionFactory
Unlike ISession, we need one instance of ISessionFactory throughout the application.using FluentNHibernate.Cfg; using FluentNHibernate.Cfg.Db; using JustBlog.Core.Objects; using NHibernate; using NHibernate.Cache; using NHibernate.Tool.hbm2ddl; using Ninject; using Ninject.Modules; using Ninject.Web.Common; namespace JustBlog.Core { public class RepositoryModule: NinjectModule { public override void Load() { Bind
() .ToMethod ( e => Fluently.Configure() .Database(MsSqlConfiguration.MsSql2008.ConnectionString(c => c.FromConnectionStringWithKey("JustBlogDbConnString"))) .Cache(c => c.UseQueryCache().ProviderClass ()) .Mappings(m => m.FluentMappings.AddFromAssemblyOf ()) .ExposeConfiguration(cfg => new SchemaExport(cfg).Execute(true, true, false)) .BuildConfiguration() .BuildSessionFactory() ) .InSingletonScope(); Bind () .ToMethod((ctx) => ctx.Kernel.Get ().OpenSession()) .InRequestScope(); } } } As the Fluent NHibernate Wiki tells us in general, setting up NHibernate using the Fluent NHibernate API is done using 5 main methods, 3 of which are required:
Fluently.Configure() .Database(/* your database settings */) .Mappings(/* your mappings */) .ExposeConfiguration(/* alter Configuration */) // optional .BuildSessionFactory();
Fluently.Configure начинает процесс настройки
Database, настраивает строку подключения к базе данных
Mappings настраивает сопоставления на основе ваших классов сущностей
ExposeConfiguration не является обязательным, но позволяет создать схему базы данных из ваших сопоставлений (создаем таблицы из наших классов)
BuildSessionFactory это последний метод, и он создает экземпляр NHibernate SessionFactory на основе настроек предыдущих методов.
The Cache method is optional; it configures the provider to cache requests.
There are many extensions available in Ninject and one of them is Ninject.Web.Common, which contains some common functionality required by WebForms and MVC applications. We need to make sure that while the query is being executed, Ninject returns the same instance of ISession. The InRequestScope () extension method defined in the Ninject.Web.Common namespace tells Ninject that for each HTTP request received by ASP.NET, only one instance of the class must be created. The InSingletonScope () extension method creates a singleton that is available throughout the application.4.6 Ninject Setup for MVC Project
All calls to the database from the controllers are made through the IBlogRepository interface . To implement an instance of a class that implements IBlogRepository in the controller, you need to configure Ninject in the MVC application. There is an extension (Ninject.Mvc3) specially created to support MVC applications.
Install Ninject.Mvc3 in our JustBlog project by running the following command. After successfully executing the commands, the following assemblies will be added to the MVC project: Ninject Ninject.Web.Common Ninject.Web.Mvc Delete the NinjectWebCommon.cs file from the App_Start folder. Inherit our global application class from the NinjectHttpApplication class and override the methodPM> Install-Package Ninject.Mvc3
CreateKernel .
Modify the Global.asax.cs file as shown below.using Ninject; using Ninject.Web.Common; using System.Web.Mvc; using System.Web.Routing; namespace JustBlog { public class MvcApplication : NinjectHttpApplication { protected override IKernel CreateKernel() { var kernel = new StandardKernel(); kernel.Load(new RepositoryModule()); kernel.Bind
().To (); return kernel; } protected override void OnApplicationStarted() { RouteConfig.RegisterRoutes(RouteTable.Routes); base.OnApplicationStarted(); } } }
In the CreateKernel method , we create and return a StandardKernel instance of type IKernel . IKernel is the core of the application where we specify our bindings and when we need an instance mapped to an interface it provides us with it.
Using an instance of the StandardKernel class, we do a couple of important things. First, we load an instance of our Repository module , which contains all the bindings associated with the NHibernate interfaces, and then bind the IBlogRepository interface directly to its BlogRepository implementation . And finally, at the end of the example, the application launch code moved to the OnApplicationStarted method.
That's all about Ninject's configuration in the app. In the next part, we will start working on controllers and views.