In this article, I will give you the basics on creating Single Page CRUD Application (SPA) using ASP.NET MVC, Entity Framework and AngularJS.
Below is the project structure of the below explained application.
Below are the screen prints of the below explained application.
Click on respective tab to view the code underneath.
Create the Models and Database Context
Under the Models folder, create the Post.cs, PostTags.cs and Tags (Db Entity).
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace dotnetfundaAngularJS.Models
{
public class Post
{
public Post()
{
this.postTags = new HashSet<PostTags>();
}
[Key]
public Int64 PostId { get; set; }
public string Author { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public DateTime CreatedDate { get; set; }
public DateTime LastmodifiedDate { get; set; }
public virtual ICollection<PostTags> postTags { get; set; }
public string Status { get; set; }
}
}
using System;
using System.ComponentModel.DataAnnotations;
namespace dotnetfundaAngularJS.Models
{
public class PostTags
{
[Key]
public Int64 PostTagId { get; set; }
public Int64 TagId { get; set; }
public Int64 PostId { get; set; }
public virtual Post Post { get; set; }
public virtual Tags Tag { get; set; }
}
}
using System;
using System.ComponentModel.DataAnnotations;
namespace dotnetfundaAngularJS.Models
{
public class Tags
{
[Key]
public Int64 TagId { get; set; }
public string Tag { get; set; }
}
}
using System;
using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;
namespace dotnetfundaAngularJS.Models
{
public class PostModel
{
public Int64 PostId { get; set; }
[AllowHtml]
[Required(ErrorMessage = "Title is required")]
public string Title { get; set; }
[AllowHtml]
[Required(ErrorMessage = "Content is required")]
public string Content { get; set; }
[Display(Name = "Tags (Comma ',' Seperated)")]
[Required(ErrorMessage = "Tags is/are required")]
public string Tags { get; set; }
public string Author { get; set; }
public DateTime CreatedDate { get; set; }
}
}
Under the Models folder, create dotnetfundaDbContext.cs (DbContext class)
using System.Data.Entity;
using System.Data.Entity.ModelConfiguration.Conventions;
namespace dotnetfundaAngularJS.Models
{
public class dotnetfundaDbContext : DbContext
{
public dotnetfundaDbContext()
: base("name=dotnetfundaDbContext")
{
Database.SetInitializer<dotnetfundaDbContext>(null);
}
public DbSet<Post> posts { get; set; }
public DbSet<Tags> tags { get; set; }
public DbSet<PostTags> posttags { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
modelBuilder.Conventions.Remove<PluralizingEntitySetNameConvention>();
}
}
}
Under the Models folder, create Utilities.cs (Utilities class)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace dotnetfundaAngularJS.Models
{
public class Utilities
{
public static string ConvertCollectionToString(ICollection<Tags> tags)
{
StringBuilder builder = new StringBuilder();
foreach (var tag in tags)
{
builder.Append(tag.Tag);
builder.Append(',');
}
return builder.ToString();
}
public static List<PostTags> ConvertToCollection(PostModel model)
{
IRepository<Tags> tagrepository = new Repository<Tags>();
List<PostTags> tag = new List<PostTags>();
string[] temptag = model.Tags.Split(';');
foreach (string tg in temptag)
{
var tags = tagrepository.SelectAll().Where(t => t.Tag == tg);
foreach (var t in tags)
{
var exist = tag.Where(p => p.TagId == Convert.ToInt64(t.TagId)).ToList();
if (exist.Count() == 0)
{
var taG = new PostTags
{
TagId = Convert.ToInt64(t.TagId)
};
tag.Add(taG);
}
}
}
return tag;
}
}
}
Create Generic Repository
Generic Repository Interface of the type class can be created as follows. If a particular repository requires additional operations, it can extend the generic repository interface.
using System.Collections.Generic;
namespace dotnetfundaAngularJS.Models
{
public interface IRepository<T> where T : class
{
IEnumerable<T> SelectAll();
T SelectByID(object id);
void Insert(T obj);
void Update(T obj);
void Delete(object id);
bool Save();
}
}
Repository.cs (Repository class)
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
namespace dotnetfundaAngularJS.Models
{
public class Repository<T> : IRepository<T> where T : class
{
private dotnetfundaDbContext db = null;
private DbSet<T> table = null;
public Repository()
{
this.db = new dotnetfundaDbContext();
table = db.Set<T>();
}
public Repository(dotnetfundaDbContext db)
{
this.db = db;
table = db.Set<T>();
}
public IEnumerable<T> SelectAll()
{
try
{
return table.ToList();
}
catch (Exception ex)
{
throw ex;
}
}
public T SelectByID(object id)
{
try
{
return table.Find(id);
}
catch (Exception ex)
{
throw ex;
}
}
public void Insert(T obj)
{
try
{
table.Add(obj);
}
catch (Exception ex)
{
throw ex;
}
}
public void Update(T obj)
{
try
{
table.Add(obj);
db.Entry(obj).State = EntityState.Modified;
}
catch (Exception ex)
{
throw ex;
}
}
public void Delete(object id)
{
try
{
T existing = table.Find(id);
table.Remove(existing);
}
catch (Exception ex)
{
throw ex;
}
}
public bool Save()
{
try
{
db.SaveChanges();
return true;
}
catch (Exception ex)
{
throw ex;
}
}
}
}
Create Controller
Create Post Controller
using dotnetfundaAngularJS.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Web.Mvc;
namespace dotnetfundaAngularJS.Controllers
{
public class PostController : Controller
{
private IRepository<Post> repository = null;
private IRepository<Tags> tagrepository = null;
private IRepository<PostTags> postagrepository = null;
private bool Success = false;
public PostController()
{
this.repository = new Repository<Post>();
this.tagrepository = new Repository<Tags>();
this.postagrepository = new Repository<PostTags>();
}
public PostController(IRepository<Post> repository,
IRepository<Tags> tagrepository, IRepository<PostTags> postagrepository)
{
this.repository = repository;
this.tagrepository = tagrepository;
this.postagrepository = postagrepository;
}
public JsonResult PostList()
{
List<PostModel> postTags = new List<PostModel>();
string tags = string.Empty;
try
{
var posts = repository.SelectAll().ToList();
foreach (Post post in posts)
{
var allpostags = postagrepository.SelectAll().Where(p => p.PostId == post.PostId);
foreach(var postag in allpostags)
{
var tagName = tagrepository.SelectAll().Where(t => t.TagId == postag.TagId).ToList();
foreach (var tag in tagName)
{
tags = tag.Tag + ";" + tags;
}
}
postTags.Add(new PostModel { PostId = post.PostId,Title=post.Title,
Content=post.Content,Tags = tags,Author = post.Author,CreatedDate=post.CreatedDate });
tags = string.Empty;
}
return Json(postTags, JsonRequestBehavior.AllowGet);
}
catch (Exception e)
{
}
return Json(postTags, JsonRequestBehavior.AllowGet);
}
public JsonResult New()
{
PostModel postModel = new PostModel();
return Json(postModel, JsonRequestBehavior.AllowGet);
}
public ActionResult NewPost(PostModel model)
{
try
{
var post = new Post
{
Author = "Himen Patel",
Title = model.Title,
Content = model.Content,
CreatedDate = DateTime.UtcNow,
postTags = Utilities.ConvertToCollection(model),
};
repository.Insert(post);
Success = repository.Save();
return new HttpStatusCodeResult(HttpStatusCode.Created);
}
catch
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
}
public JsonResult Edit(string id)
{
PostModel postTags = new PostModel();
string tags = string.Empty;
try
{
var posts = repository.SelectAll().Where(p => p.PostId == Convert.ToInt64(id)).ToList();
foreach (Post post in posts)
{
var allpostags = postagrepository.SelectAll().Where(p => p.PostId == post.PostId);
foreach (var postag in allpostags)
{
var tagName = tagrepository.SelectAll().Where(t => t.TagId == postag.TagId).ToList();
foreach (var tag in tagName)
{
tags = tag.Tag + ";" + tags;
}
}
postTags.Title = post.Title;
postTags.Content = post.Content;
postTags.Tags = tags;
postTags.PostId = post.PostId;
tags = string.Empty;
}
return Json(postTags, JsonRequestBehavior.AllowGet);
}
catch(Exception ex)
{
}
return Json(postTags, JsonRequestBehavior.AllowGet);
}
public ActionResult EditPost(PostModel model)
{
try
{
bool success = DeleteTagsByPostId(Convert.ToInt32(model.PostId));
var post = new Post
{
PostId = model.PostId,
Author = "Himen Patel",
Title = model.Title,
Content = model.Content,
CreatedDate = DateTime.UtcNow,
postTags = Utilities.ConvertToCollection(model),
};
repository.Update(post);
Success = repository.Save();
return new HttpStatusCodeResult(HttpStatusCode.Created);
}
catch
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
}
public ActionResult Delete(string PostId)
{
try
{
bool success = DeleteTagsByPostId(Convert.ToInt32(PostId));
if (success)
{
repository.Delete(Convert.ToInt32(PostId));
Success = repository.Save();
}
return new HttpStatusCodeResult(HttpStatusCode.Created);
}
catch
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
}
[NonAction]
public bool DeleteTagsByPostId(int id)
{
try
{
var tags = postagrepository.SelectAll().Where(t => t.PostId == id);
foreach (var t in tags)
{
postagrepository.Delete(t.PostTagId);
}
Success = postagrepository.Save();
return Success;
}
catch (Exception ex)
{
throw ex;
}
}
}
}
Create Tags Controller
using dotnetfundaAngularJS.Models;
using System.Linq;
using System.Web.Mvc;
namespace dotnetfundaAngularJS.Controllers
{
public class TagsController : Controller
{
private IRepository<Tags> repository = null;
public TagsController()
{
this.repository = new Repository<Tags>();
}
public TagsController(IRepository<Tags> repository)
{
this.repository = repository;
}
public JsonResult TagsList()
{
var TagsList = repository.SelectAll().ToList();
return Json(TagsList, JsonRequestBehavior.AllowGet);
}
}
}
Create Angular Scripts As Below
Create module.js and paste the below code snippet.
var app = angular.module('blogApp',
['ngRoute', 'ngResource', 'ui.bootstrap']);
Create route.js and paste the below code snippet.
app.config(['$routeProvider', function ($routeProvider) {
$routeProvider.when('/', {
templateUrl: "/app/Home/home.html"
}),
$routeProvider.when('/post', {
templateUrl: "/app/post/PostList.html",
controller: "postListController"
}),
$routeProvider.when('/post/new', {
templateUrl: "/app/post/Post.html",
controller: "postController"
}),
$routeProvider.when('/post/:id', {
templateUrl: "/app/post/Post.html",
controller: "postController"
}),
$routeProvider.otherwise({
redirectTo: '/'
});
}]);
Create dataServices.js and paste the below code snippet.
app.service("PostService", function ($http) {
this.getPosts = function () {
return $http.get("Post/PostList");
};
this.getNewPost = function () {
return $http.get("Post/New");
};
this.getPost = function (id) {
var response = $http({
method: "post",
url: "Post/Edit",
params: {
id: id
}
});
return response;
}
this.updatePost = function (Post) {
var response = $http({
method: "post",
url: "Post/EditPost",
data: JSON.stringify(Post),
dataType: "json"
});
return response;
}
this.newPost = function (Post) {
var response = $http({
method: "post",
url: "Post/NewPost",
data: JSON.stringify(Post),
dataType: "json"
});
return response;
}
this.Delete = function (PostId) {
var response = $http({
method: "post",
url: "Post/Delete",
params: {
PostId: JSON.stringify(PostId)
}
});
return response;
}
});
app.service("TagsService", function ($http) {
this.getPosts = function () {
return $http.get("Tags/TagsList");
};
});
Create postController.js and paste the below code snippet.
app.controller("postListController",
['$scope', 'PostService', '$window', '$routeParams',
function ($scope, PostService, $window, $routeParams) {
GetAllPosts();
function GetAllPosts() {
var getData = PostService.getPosts();
getData.then(function (pst) {
$scope.posts = pst.data;
}, function () {
$scope.message = 'Unable to load post data: ' + error.message;
});
}
$scope.deletePost = function (postId) {
var getData = PostService.Delete(postId);
getData.then(function (msg) {
$scope.message = 'Post Successfully Deleted.';
}, function () {
$scope.message = 'Unable to delete post: ' + error.message;
});
}
}]);
app.controller("postController",
['$scope', 'PostService', '$window', '$routeParams',
function ($scope, PostService, $window, $routeParams) {
$scope.post = {};
if ($routeParams.id) {
var getData = PostService.getPost($routeParams.id);
getData.then(function (pst) {
$scope.post = pst.data;
$scope.PostId = pst.data.PostId;
$scope.Title = pst.data.Title;
$scope.Content = pst.data.Content;
$scope.Tags = pst.data.Tags;
}, function () {
$scope.message = 'Unable to load post data: ' + error.message;
});
}
$scope.AddUpdatePost = function () {
var Post = {
Title: $scope.Title,
Content: $scope.Content,
Tags: $scope.Tags,
PostId: $scope.PostId
};
if (Post.PostId != null) {
var getData = PostService.updatePost(Post);
getData.then(function (msg) {
$window.location = "#/post";
$scope.message = 'Post Successfully Updated.';
}, function () {
$scope.message = 'Unable to update post: ' + error.message;
});
} else {
var getData = PostService.newPost(Post);
getData.then(function (msg) {
$window.location = "#/post";
$scope.message = 'Post Successfully Created.';
}, function () {
$scope.message = 'Unable to create new post: ' + error.message;
});
}
}
$scope.addtag = function (param) {
if ($scope.Tags != null) {
$scope.Tags = param + ';' + $scope.Tags;
}
else { $scope.Tags = param; }
};
}]);
app.controller("tagsController", function ($scope, TagsService) {
GetAllTags();
function GetAllTags() {
var getData = TagsService.getPosts()
getData.then(function (tags) {
$scope.tags = tags.data;
}, function () {
$scope.message = 'Unable to load tags: ' + error.message;
});
}
});
Html/Views
Create PostList.html and paste the below code snippet:
<div>
<br />
<div class="panel panel-default">
<div class="panel-heading">Posts | All</div>
<div class="panel-body">
<div class="input-group">
<a class="btn btn-primary"
href="#/post/new" ng-click="newPost()">
New Post</a>
<input type="text" id="searchText"
class="form-control pull-right"
placeholder="Search for..." ng-model="search_txt">
<span class="input-group-btn">
<button class="btn btn-primary" type="button">
<i class="text-muted glyphicon glyphicon-search"></i>
</button>
</span>
</div><!--
<div><p style="text-align:center;
color:red">{{message}}</p></div>
<div class="table table-responsive">
<table class="table table-striped">
<thead style="background-color:#333;color:#fff">
<tr>
<th>Title</th>
<th>Content</th>
<th>Tags</th>
<th>Author</th>
<th>Created Date</th>
<th>Edit</th>
<th>Delete</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="post in posts | filter:search_txt">
<td>
{{post.Title}}
</td>
<td>
{{post.Content}}
</td>
<td>{{post.Tags}}</td>
<td>{{post.Author}}</td>
<td>{{post.CreatedDate.slice(6, -2) | date: 'yyyy-MM-dd' }}</td>
<td>
<a class="btn btn-primary" href="#/post/{{post.PostId}}">
<i class="glyphicon glyphicon-edit"></i>
</a>
</td>
<td>
<a id="deleteButton" name="submitForm" class="btn btn-danger"
ng-click="deletePost(post.PostId)">
<i class="glyphicon glyphicon-trash"></i> Delete
</a>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
Create post.html and paste the below code snippet:
<div><br />
<div class="panel panel-default">
<div class="panel-heading" ng-if="PostId">Posts | Update Post</div>
<div class="panel-heading" ng-if="!PostId">Posts | New Post</div>
<div><p style="text-align:center;color:red">{{message}}</p></div>
<table>
<tr>
<td class="control-label col-md-2"><b>Title</b></td>
<td>
<input class="form-control" type="text"
ng-required="true" ng-model="Title" placeholder="Title" />
</td>
</tr>
<tr>
<td class="control-label col-md-2"><b>Content</b></td>
<td>
<input class="form-control" type="text"
ng-model="Content" placeholder="Content" />
</td>
</tr>
<tr>
<td class="control-label col-md-2"><b>Tags</b></td>
<td>
<input class="form-control" type="text"
ng-required="true" ng-model="Tags" placeholder="Tags" />
</td>
</tr>
<tr ng-controller="tagsController">
<td class="control-label col-md-2"><b>Select Tags</b></td>
<td>
<span ng-repeat="tag in tags">
<a ng-click="addtag(tag.Tag)" ng-model="tag.Tag">
{{tag.Tag}}</a>, </span>
</td>
</tr>
<tr>
<td></td>
<td>
<button id="submitButton" name="submitButton"
class="btn btn-primary" ng-click="AddUpdatePost()">Save</button>
<a class="btn btn-warning" href="#/post">Cancel</a>
</td>
</tr>
</table>
<input type="hidden" ng-model="PostId" />
</div>
</div>
Include your angular scripts files in _Layout.cshtml as below or add them in BundleConfig.cs file.
<html ng-app="blogApp">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>@ViewBag.Title - My ASP.NET Application</title>
@Styles.Render("~/Content/css")
@Scripts.Render("~/bundles/modernizr")
@Scripts.Render("~/bundles/angular")
@Scripts.Render("~/bundles/MyblogApp")
</head>
Replace Home/Index.html code with the below code snippet:
<div class="ng-view"> </div>