diff --git a/ControllersLogic/ControllersLogic.csproj b/ControllersLogic/ControllersLogic.csproj new file mode 100644 index 0000000..83cfc9a --- /dev/null +++ b/ControllersLogic/ControllersLogic.csproj @@ -0,0 +1,82 @@ + + + + + Debug + AnyCPU + {89FC8B31-B22A-40DD-981E-89A86D96BE34} + Library + Properties + ControllersLogic + ControllersLogic + v4.5.2 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\CloudinaryDotNet.1.0.24\lib\net40\CloudinaryDotNet.dll + True + + + ..\packages\MongoDB.Bson.2.2.4\lib\net45\MongoDB.Bson.dll + True + + + ..\packages\Newtonsoft.Json.4.5.11\lib\net40\Newtonsoft.Json.dll + True + + + + + + + + + + + + + + + + + + + + + + + {4841ab80-72fd-4f53-bb68-28b111786669} + DB + + + + + + + + + \ No newline at end of file diff --git a/ControllersLogic/Interfaces/ICommentLogic.cs b/ControllersLogic/Interfaces/ICommentLogic.cs new file mode 100644 index 0000000..3dffc66 --- /dev/null +++ b/ControllersLogic/Interfaces/ICommentLogic.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using DB.Models; + +namespace ControllersLogic.Interfaces +{ + public interface ICommentLogic + { + String AddComment(String text, String name); + List GetAllComment(); + CommentWithoutObjectId GetById(String id); + CommentWithoutObjectId UpdateById(String id, String name, String text); + } +} diff --git a/ControllersLogic/Interfaces/IImageLogic.cs b/ControllersLogic/Interfaces/IImageLogic.cs new file mode 100644 index 0000000..7d7c350 --- /dev/null +++ b/ControllersLogic/Interfaces/IImageLogic.cs @@ -0,0 +1,23 @@ +using DB.Models; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Web; + +namespace ControllersLogic.Interfaces +{ + public interface IImageLogic + { + String AddCommentToImage(String simageId, String text, String name); + String AddImage(String url, String name); + String DeleteCommentFromImage(String idImage, String idComment); + String DownloadImage(HttpPostedFileBase uploadImage); + List GetAllImage(); + ImageWithoutObjectId GetById(String id); + ImageWithoutObjectId GetByIdAndVersion(String id, int version); + List GetCommentFromImage(String simageId); + ImageWithoutObjectId UpdateById(String id, String name, String url); + } +} diff --git a/ControllersLogic/Interfaces/IProjectLogic.cs b/ControllersLogic/Interfaces/IProjectLogic.cs new file mode 100644 index 0000000..51f2b30 --- /dev/null +++ b/ControllersLogic/Interfaces/IProjectLogic.cs @@ -0,0 +1,23 @@ +using DB.Models; +using System; +using System.Collections.Generic; + +namespace ControllersLogic.Interfaces +{ + public interface IProjectLogic + { + String AddCommentToProject(String text, String name, String sprojectId); + String AddImageToProject(String url, String name, String sprojectId); + String AddProject(String name); + String AddProjectToProject(String sidRoot, String name); + String DeleteById(String id); + String DeleteCommentFromProject(String projectId, String commentId); + String DeleteImageFromProject(String projectId, String imageId); + List GetAllProjects(); + ProjectWithoutObjectId GetById(String id); + List GetCommentsFormProject(String sidRoot); + List GetImagesFormProject(String sidRoot); + List GetPtojectsFormProject(String sidRoot); + ProjectWithoutObjectId UpdateById(String id, String name); + } +} diff --git a/ControllersLogic/Logic/CommentLogic.cs b/ControllersLogic/Logic/CommentLogic.cs new file mode 100644 index 0000000..095f94a --- /dev/null +++ b/ControllersLogic/Logic/CommentLogic.cs @@ -0,0 +1,73 @@ +using ControllersLogic.Interfaces; +using DB.Interfaces; +using DB.Models; +using MongoDB.Bson; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace ControllerLogic +{ + public class CommentLogic: ICommentLogic + { + private static ICommentRepository _commentRepository = new DB.Repositories.DBCommentRepository(); + public String AddComment(String text, String name) + { + Comment comment = new Comment(); + comment.Text = text; + comment.CreationelData = DateTime.UtcNow; + comment.Name = name; + comment.Version = 1; + var id = _commentRepository.AddComment(comment).Id.ToString(); + return id; + } + public List GetAllComment() + { + var comments = CommentWithoutObjectId.CommentsToCommentWithoutObjectId(_commentRepository.GetAll()); + return comments; + } + public CommentWithoutObjectId GetById(String id) + { + var objectId = new ObjectId(); + if (!ObjectId.TryParse(id, out objectId)) + { + throw new Exception("This isn't objectId"); + } + if (objectId == null) + { + throw new Exception("Bad id"); + } + var comment = CommentWithoutObjectId.CommentToCommentWithoutObjectId(_commentRepository.GetCommentById(objectId)); + if (comment == null) + { + throw new Exception("Bad id"); + } + return comment; + } + public CommentWithoutObjectId UpdateById(String id, String name, String text) + { + var objectId = new ObjectId(); + if (!ObjectId.TryParse(id, out objectId)) + { + throw new Exception("This isn't objectId"); + } + if (objectId == null) + { + throw new Exception("Bad id"); + } + var comment = _commentRepository.GetCommentById(objectId); + if (comment == null) + { + throw new Exception("Bad id"); + } + comment.Name = name; + comment.Text = text; + comment.Version++; + _commentRepository.DeleteById(objectId); + _commentRepository.AddComment(comment); + return CommentWithoutObjectId.CommentToCommentWithoutObjectId(comment); + } + } +} diff --git a/ControllersLogic/Logic/ImageLogic.cs b/ControllersLogic/Logic/ImageLogic.cs new file mode 100644 index 0000000..21b55a1 --- /dev/null +++ b/ControllersLogic/Logic/ImageLogic.cs @@ -0,0 +1,176 @@ +using ControllersLogic.Interfaces; +using DB.Interfaces; +using DB.Models; +using MongoDB.Bson; +using System; +using System.Collections.Generic; +using System.IO; +using System.Web; + +namespace ControllersLogic.Logic +{ + public class ImageLogic : IImageLogic + { + private IImageRepository _imageRepository = new DB.Repositories.DBImageRepository(); + private ICommentRepository _commentRepository = new DB.Repositories.DBCommentRepository(); + + public String AddCommentToImage(String simageId, String text, String name) + { + Comment comment = new Comment(); + comment.Text = text; + comment.CreationelData = DateTime.UtcNow; + comment.Name = name; + comment.Version = 1; + var commentId = _commentRepository.AddComment(comment).Id; + var imageId = new ObjectId(); + if (!ObjectId.TryParse(simageId, out imageId)) + { + return "Bad id it's not objectId"; + } + if (_imageRepository.GetImageById(imageId) == null) + { + return "Bad id image"; + } + _imageRepository.AddCommentToImage(commentId, imageId); + return commentId.ToString(); + } + public String AddImage(String url, String name) + { + Image image = new Image(); + image.Url = url; + image.Version = 1; + image.Name = name; + image.CreationelData = DateTime.UtcNow; + var id = _imageRepository.AddImage(image).Id; + image.StartId = id.ToString(); + _imageRepository.DeleteById(id); + _imageRepository.AddImage(image); + return id.ToString(); + } + public String DeleteCommentFromImage(String idImage, String idComment) + { + var objectIdImage = new ObjectId(); + var objectIdComment = new ObjectId(); + if (!ObjectId.TryParse(idImage, out objectIdImage)) + { + return "Bad image id"; + } + if (!ObjectId.TryParse(idComment, out objectIdComment)) + { + return "Bad comment id"; + } + if (_imageRepository.GetImageById(objectIdImage) == null) + { + return "Bad image id, not found in DB"; + } + _imageRepository.DeleteCommentFromImage(objectIdImage, objectIdComment); + _commentRepository.DeleteById(objectIdComment); + return "OK"; + } + public String DownloadImage(HttpPostedFileBase uploadImage) + { + if (uploadImage != null) + { + string fileName = Path.GetFileName(uploadImage.FileName); + var input = uploadImage.InputStream; + CloudinaryDotNet.Account account = new CloudinaryDotNet.Account("hzvwvtbls", "482455376217895", "bXPz-CiQrEjZp4xqSV8UK_nfI2c"); + CloudinaryDotNet.Cloudinary cloudinary = new CloudinaryDotNet.Cloudinary(account); + CloudinaryDotNet.Actions.ImageUploadParams uploadParams = new CloudinaryDotNet.Actions.ImageUploadParams() + { + File = new CloudinaryDotNet.Actions.FileDescription(fileName, input) + }; + CloudinaryDotNet.Actions.ImageUploadResult uploadResult = cloudinary.Upload(uploadParams); + string url = cloudinary.Api.UrlImgUp.BuildUrl(String.Format("{0}.{1}", uploadResult.PublicId, uploadResult.Format)); + return url; + } + else + { + return "Bad file"; + } + } + public List GetAllImage() + { + var images = ImageWithoutObjectId.ImagesToImageWithoutObjectId(_imageRepository.GetAllImage()); + return images; + } + public ImageWithoutObjectId GetById(String id) + { + var objectId = new ObjectId(); + if (!ObjectId.TryParse(id, out objectId)) + { + throw new Exception("Bad id it's not objectId"); + } + if (objectId == null) + { + throw new Exception("Bad id"); + } + var image = ImageWithoutObjectId.ImageToImageWithoutObjectId(_imageRepository.GetImageById(objectId)); + if (image == null) + { + throw new Exception("Bad id"); + } + return image; + } + public ImageWithoutObjectId GetByIdAndVersion(String id, int version) + { + var objectId = new ObjectId(); + if (!ObjectId.TryParse(id, out objectId)) + { + throw new Exception("Bad id it's not objectId"); + } + if (objectId == null) + { + throw new Exception("Bad id"); + } + var image = ImageWithoutObjectId.ImageToImageWithoutObjectId(_imageRepository.GetImageByIdAndVersion(objectId, version)); + if (image == null) + { + throw new Exception("Bad id"); + } + return image; + } + public List GetCommentFromImage(String simageId) + { + var imageId = new ObjectId(); + if (!ObjectId.TryParse(simageId, out imageId)) + { + throw new Exception("Bad id it's not objectId"); + } + var image = _imageRepository.GetImageById(imageId); + if (image == null) + { + throw new Exception("Bad id image, this image don't found in DB"); + } + var comments = CommentWithoutObjectId.CommentsToCommentWithoutObjectId(_commentRepository.GetCommentsByIds(image.Comments)); + return comments; + } + public ImageWithoutObjectId UpdateById(String id, String name, String url) + { + var objectId = new ObjectId(); + if (!ObjectId.TryParse(id, out objectId)) + { + throw new Exception("Bad id it's not objectId"); + } + if (objectId == null) + { + throw new Exception("Bad id"); + } + var image = _imageRepository.GetImageById(objectId); + Image prev_image = _imageRepository.GetImageById(objectId); + if (image == null) + { + throw new Exception("Bad id"); + } + image.Url = url; + image.Version++; + image.Name = name; + image.StartId = id; + _imageRepository.DeleteById(objectId); + _imageRepository.AddImage(image); + prev_image.Id = new ObjectId(); + prev_image.StartId = id; + _imageRepository.AddImage(prev_image); + return ImageWithoutObjectId.ImageToImageWithoutObjectId(image); + } + } +} diff --git a/ControllersLogic/Logic/ProjectLogic.cs b/ControllersLogic/Logic/ProjectLogic.cs new file mode 100644 index 0000000..170a3a4 --- /dev/null +++ b/ControllersLogic/Logic/ProjectLogic.cs @@ -0,0 +1,237 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using ControllersLogic.Interfaces; +using DB.Interfaces; +using DB.Repositories; +using DB.Models; +using MongoDB.Bson; + +namespace ControllersLogic.Logic +{ + public class ProjectLogic : IProjectLogic + { + private IProjectRepository _projectRepository = new DBProjectRepository(); + private IImageRepository _imageRepository = new DBImageRepository(); + private ICommentRepository _commentRepository = new DBCommentRepository(); + + public String AddCommentToProject(String text, String name, String sprojectId) + { + var projectId = new ObjectId(); + if (!ObjectId.TryParse(sprojectId, out projectId)) + { + return "Bad id it's not objectId"; + } + if (_projectRepository.GetProjectById(projectId) == null) + { + return "Bad id it's not found in DB" ; + } + Comment comment = new Comment(); + comment.Text = text; + comment.CreationelData = DateTime.UtcNow; + comment.Name = name; + comment.Version = 1; + var commentId = _commentRepository.AddComment(comment).Id; + _projectRepository.AddCommentToProject(commentId, projectId); + return commentId.ToString(); + } + public String AddImageToProject(String url, String name, String sprojectId) + { + var projectId = new ObjectId(); + if (!ObjectId.TryParse(sprojectId, out projectId)) + { + return "Bad id it's not objectId"; + } + if (_projectRepository.GetProjectById(projectId) == null) + { + return "Bad id it's not found in DB"; + } + Image image = new Image(); + image.Url = url; + image.Version = 1; + image.Name = name; + image.CreationelData = DateTime.UtcNow; + var imageId = _imageRepository.AddImage(image).Id; + _projectRepository.AddImageToProject(imageId, projectId); + return imageId.ToString(); + } + public String AddProject(String name) + { + Project project = new Project(); + project.Name = name; + project.Version = 1; + project.CreationelData = DateTime.UtcNow; + var id = _projectRepository.AddProject(project).Id; + return id.ToString(); + } + public String AddProjectToProject(String sidRoot, String name) + { + var idRoot = new ObjectId(); + if (!ObjectId.TryParse(sidRoot, out idRoot)) + { + return "Bad id it's not objectId"; + } + if (_projectRepository.GetProjectById(idRoot) == null) + { + return "Bad id it's not found in DB" ; + } + Project project = new Project(); + project.Name = name; + project.Version = 1; + project.CreationelData = DateTime.UtcNow; + var idNew = _projectRepository.AddProject(project).Id; + _projectRepository.AddProjectToProject(idNew, idRoot); + return idNew.ToString(); + } + public String DeleteById(String id) + { + var objectId = new ObjectId(); + if (!ObjectId.TryParse(id, out objectId)) + { + return "Bad id it's not objectId" ; + } + if (objectId == null) + { + return "Bad id"; + } + if (_projectRepository.GetProjectById(objectId) == null) + { + return "Bad id"; + } + _projectRepository.DeleteById(objectId); + return "OK"; + } + public String DeleteCommentFromProject(String projectId, String commentId) + { + var objectIdProject = new ObjectId(); + var objectIdComment = new ObjectId(); + if (!ObjectId.TryParse(projectId, out objectIdProject)) + { + return "Bad id project it's not objectId"; + } + if (!ObjectId.TryParse(commentId, out objectIdComment)) + { + return "Bad id coomment it's not objectId"; + } + if (_projectRepository.GetProjectById(objectIdProject) == null) + { + return "Bad id"; + } + _projectRepository.DeleteCommentFromProject(objectIdProject, objectIdComment); + _commentRepository.DeleteById(objectIdComment); + return "OK"; + } + public String DeleteImageFromProject(String projectId, String imageId) + { + var objectIdProject = new ObjectId(); + var objectIdImage = new ObjectId(); + if (!ObjectId.TryParse(projectId, out objectIdProject)) + { + return "Bad id project it's not objectId"; + } + if (!ObjectId.TryParse(imageId, out objectIdImage)) + { + return "Bad id image it's not objectId"; + } + if (_projectRepository.GetProjectById(objectIdProject) == null) + { + return "Bad id"; + } + _projectRepository.DeleteImageFromProject(objectIdProject, objectIdImage); + _imageRepository.DeleteById(objectIdImage); + return "OK"; + } + public List GetAllProjects() + { + var projects = ProjectWithoutObjectId.ProjectsToProjectWithoutObjectId(_projectRepository.GetAllProject()); + return projects; + } + public ProjectWithoutObjectId GetById(String id) + { + var objectId = new ObjectId(); + if (!ObjectId.TryParse(id, out objectId)) + { + throw new Exception("Bad id it's not objectId"); + } + if (objectId == null) + { + throw new Exception("Bad id"); + } + var project = ProjectWithoutObjectId.ProjectToProjectWithoutObjectId(_projectRepository.GetProjectById(objectId)); + if (project == null) + { + throw new Exception("Bad id"); + } + return project; + } + public List GetCommentsFormProject(String sidRoot) + { + var idRoot = new ObjectId(); + if (!ObjectId.TryParse(sidRoot, out idRoot)) + { + throw new Exception("Bad id it's not objectId"); + } + var project = _projectRepository.GetProjectById(idRoot); + if (project == null) + { + throw new Exception("Bad id it's not found in DB"); + } + var comments = CommentWithoutObjectId.CommentsToCommentWithoutObjectId(_commentRepository.GetCommentsByIds(project.Comments)); + return comments; + } + public List GetImagesFormProject(String sidRoot) + { + var idRoot = new ObjectId(); + if (!ObjectId.TryParse(sidRoot, out idRoot)) + { + throw new Exception("Bad id it's not objectId" ); + } + var project = _projectRepository.GetProjectById(idRoot); + if (project == null) + { + throw new Exception("Bad id it's not found in DB"); + } + var images = ImageWithoutObjectId.ImagesToImageWithoutObjectId(_imageRepository.GetImagesByIds(project.Images)); + return images; + } + public List GetPtojectsFormProject(String sidRoot) + { + var idRoot = new ObjectId(); + if (!ObjectId.TryParse(sidRoot, out idRoot)) + { + throw new Exception("Bad id it's not objectId"); + } + var project = _projectRepository.GetProjectById(idRoot); + if (project == null) + { + throw new Exception("Bad id it's not found in DB"); + } + var projects = ProjectWithoutObjectId.ProjectsToProjectWithoutObjectId(_projectRepository.GetProjectsByIds(project.Projects)); + return projects; + } + public ProjectWithoutObjectId UpdateById(String id, String name) + { + var objectId = new ObjectId(); + if (!ObjectId.TryParse(id, out objectId)) + { + throw new Exception("Bad id it's not objectId"); + } + if (objectId == null) + { + throw new Exception("Bad id"); + } + var project = _projectRepository.GetProjectById(objectId); + if (project == null) + { + throw new Exception("Bad id"); + } + project.Name = name; + project.Version++; + _projectRepository.DeleteById(objectId); + _projectRepository.AddProject(project); + return ProjectWithoutObjectId.ProjectToProjectWithoutObjectId(project); + } + } +} diff --git a/ControllersLogic/Properties/AssemblyInfo.cs b/ControllersLogic/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..6f17cac --- /dev/null +++ b/ControllersLogic/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("ControllersLogic")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("ControllersLogic")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("89fc8b31-b22a-40dd-981e-89a86d96be34")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/ControllersLogic/packages.config b/ControllersLogic/packages.config new file mode 100644 index 0000000..a6059cc --- /dev/null +++ b/ControllersLogic/packages.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/DB/Interfaces/IImageRepository.cs b/DB/Interfaces/IImageRepository.cs index 0c1538f..703ecd9 100644 --- a/DB/Interfaces/IImageRepository.cs +++ b/DB/Interfaces/IImageRepository.cs @@ -15,6 +15,7 @@ public interface IImageRepository void DeleteCommentFromImage(ObjectId imageId, ObjectId commentId); Image AddImage(Image image); Image GetImageById(ObjectId id); + Image GetImageByIdAndVersion(ObjectId id, int version); List GetAllImage(); void AddCommentToImage(ObjectId newComments, ObjectId idImage); List GetImagesByIds(List ids); diff --git a/DB/Interfaces/IProjectRepository.cs b/DB/Interfaces/IProjectRepository.cs index 06b2026..803e715 100644 --- a/DB/Interfaces/IProjectRepository.cs +++ b/DB/Interfaces/IProjectRepository.cs @@ -13,6 +13,7 @@ public interface IProjectRepository void DeleteById(ObjectId id); void DeleteAll(); void DeleteCommentFromProject(ObjectId projectId, ObjectId commentId); + void DeleteProjectFromProject(ObjectId rootProjectId, ObjectId deletedProjectId); void DeleteImageFromProject(ObjectId projectId, ObjectId imageId); Project AddProject(Project project); Project GetProjectById(ObjectId id); diff --git a/DB/Models/BaseEntity.cs b/DB/Models/BaseEntity.cs index 7c2b5fb..bdadcc3 100644 --- a/DB/Models/BaseEntity.cs +++ b/DB/Models/BaseEntity.cs @@ -13,6 +13,7 @@ public class BaseEntity public ObjectId Author { get; set; } public uint Version { get; set; } public String Name { get; set; } + public DateTime LastModified { get; set; } public DateTime CreationelData { get; set; } } } diff --git a/DB/Models/Image.cs b/DB/Models/Image.cs index 4550518..781c1f1 100644 --- a/DB/Models/Image.cs +++ b/DB/Models/Image.cs @@ -11,6 +11,7 @@ public class Image:BaseEntity { public List Comments { get; set; } public String Url { get; set; } + public String StartId { get; set; } public Image() { this.Comments = new List(); @@ -36,7 +37,7 @@ public static ImageWithoutObjectId ImageToImageWithoutObjectId(Image image) Url = image.Url, Version = image.Version, Name = image.Name, - Id = image.Id.ToString(), + Id = image.StartId, CreationelData = image.CreationelData, Comments = new List()}; if (image.Comments != null) diff --git a/DB/MongoClientFactory.cs b/DB/MongoClientFactory.cs index 6e17037..a25c8e3 100644 --- a/DB/MongoClientFactory.cs +++ b/DB/MongoClientFactory.cs @@ -12,21 +12,18 @@ namespace DB { public class MongoClientFactory { - public static MongoUrl GetMongoUrl() - { - return new MongoUrl(ConfigurationManager.AppSettings.Get("MONGOLAB_URI")); - } - public static MongoServer GetMongoServer() - { - var client = new MongoClient(GetMongoUrl()); - var server = client.GetServer(); - return server; - + public static MongoClient GetMongoClient() + { + var dbConnectionString = System.Configuration.ConfigurationManager.AppSettings["DB"]; + if (!String.IsNullOrEmpty(dbConnectionString)) + { + return new MongoClient(dbConnectionString); + } + return new MongoClient(); } - public static MongoDatabase GetMongoDatabase() + public static IMongoDatabase GetMongoDatabase(String name = "mongodb") { - var database = GetMongoServer().GetDatabase(GetMongoUrl().DatabaseName); - return database; + return GetMongoClient().GetDatabase(name); } } } diff --git a/DB/Repositories/DBCommentRepository.cs b/DB/Repositories/DBCommentRepository.cs index c9e16e7..c4f9db6 100644 --- a/DB/Repositories/DBCommentRepository.cs +++ b/DB/Repositories/DBCommentRepository.cs @@ -14,7 +14,7 @@ namespace DB.Repositories { public class DBCommentRepository : ICommentRepository { - private readonly MongoCollection _commentCollection; + private readonly IMongoCollection _commentCollection; public DBCommentRepository() { @@ -23,21 +23,21 @@ public DBCommentRepository() } public void DeleteById(ObjectId id) { - _commentCollection.Remove(Query.EQ("_id", id)); + _commentCollection.DeleteOne(c => c.Id.Equals(id)); } public void DeleteAll() { - _commentCollection.RemoveAll(); + _commentCollection.DeleteMany(c => true); } public Comment AddComment(Comment comment) { - _commentCollection.Insert(comment); + _commentCollection.InsertOne(comment); return comment; } public List GetAll() { - return _commentCollection.FindAll().ToList(); + return _commentCollection.AsQueryable().ToList(); } public Comment GetCommentById(ObjectId id) @@ -47,7 +47,7 @@ public Comment GetCommentById(ObjectId id) } public List GetCommentsByIds(List ids) { - return _commentCollection.FindAll().Where(item => ids.Contains(item.Id)).ToList(); + return _commentCollection.AsQueryable().Where(item => ids.Contains(item.Id)).ToList(); } } } diff --git a/DB/Repositories/DBImageRepository.cs b/DB/Repositories/DBImageRepository.cs index 81d7fc2..6e7813b 100644 --- a/DB/Repositories/DBImageRepository.cs +++ b/DB/Repositories/DBImageRepository.cs @@ -14,7 +14,7 @@ namespace DB.Repositories { public class DBImageRepository : IImageRepository { - private readonly MongoCollection _imageCollection; + private readonly IMongoCollection _imageCollection; public DBImageRepository() { @@ -23,30 +23,32 @@ public DBImageRepository() } public void DeleteAll() { - _imageCollection.RemoveAll(); + _imageCollection.DeleteMany(prop => true); } public void DeleteById(ObjectId id) { - _imageCollection.Remove(Query.EQ("_id", id)); + _imageCollection.DeleteOne(prop => prop.Id.Equals(id)); } public void DeleteCommentFromImage(ObjectId imageId, ObjectId commentId) { - var image = _imageCollection.AsQueryable().FirstOrDefault(i => i.Id.Equals(imageId)); - _imageCollection.Remove(Query.EQ("_id", imageId)); - image.Comments.Remove(commentId); - _imageCollection.Insert(image); + var list = _imageCollection.AsQueryable().FirstOrDefault(p => p.Id.Equals(imageId)).Comments; + list.Remove(commentId); + var update = Builders.Update + .Set("Comments", list) + .CurrentDate("LastModified"); + var result = _imageCollection.UpdateOne(prop => prop.Id.Equals(imageId), update); } public void AddCommentToImage(ObjectId newComments, ObjectId idImage) { - var image = _imageCollection.AsQueryable().FirstOrDefault(im => im.Id.Equals(idImage)); - _imageCollection.Remove(Query.EQ("Id", idImage)); - image.Comments.Add(newComments); - _imageCollection.Insert(image); + var update = Builders.Update + .AddToSet("Comments", newComments) + .CurrentDate("LastModified"); + var result = _imageCollection.UpdateOne(p => p.Id.Equals(idImage), update); } public Image AddImage(Image image) { - _imageCollection.Insert(image); + _imageCollection.InsertOne(image); return image; } @@ -60,23 +62,14 @@ public Image GetImageById(ObjectId id) { return _imageCollection.AsQueryable().FirstOrDefault(im => im.Id.Equals(id)); } + public Image GetImageByIdAndVersion(ObjectId id, int version) + { + return _imageCollection.AsQueryable().FirstOrDefault(im => im.StartId.Equals(id) && im.Version.Equals(version)); + } + public List GetImagesByIds(List ids) { - var list = _imageCollection.FindAll().ToList(); - HashSet id = new HashSet(); - foreach (ObjectId i in ids) - { - id.Add(i); - } - var images = new List(); - foreach (Image i in list) - { - if (id.Contains(i.Id)) - { - images.Add(i); - } - } - return images; + return _imageCollection.AsQueryable().Where(i => ids.Contains(i.Id)).ToList(); } } } diff --git a/DB/Repositories/DBProjectRepository.cs b/DB/Repositories/DBProjectRepository.cs index b144120..f6da42f 100644 --- a/DB/Repositories/DBProjectRepository.cs +++ b/DB/Repositories/DBProjectRepository.cs @@ -14,7 +14,7 @@ namespace DB.Repositories { public class DBProjectRepository : IProjectRepository { - private readonly MongoCollection _projectCollection; + private readonly IMongoCollection _projectCollection; public DBProjectRepository() { @@ -23,37 +23,50 @@ public DBProjectRepository() } public void DeleteById(ObjectId id) { - _projectCollection.Remove(Query.EQ("_id", id)); + _projectCollection.DeleteOne(p => p.Id.Equals(id)); } public void DeleteAll() { - _projectCollection.RemoveAll(); + _projectCollection.DeleteMany(p => true); } public void DeleteCommentFromProject(ObjectId projectId, ObjectId commentId) { - var project = _projectCollection.AsQueryable().FirstOrDefault(p=>p.Id.Equals(projectId)); - _projectCollection.Remove(Query.EQ("_id", projectId)); - project.Comments.Remove(commentId); - _projectCollection.Insert(project); + var list = _projectCollection.AsQueryable().FirstOrDefault(p => p.Id.Equals(projectId)).Comments; + list.Remove(commentId); + var update = Builders.Update + .Set("Comments", list) + .CurrentDate("lastModified"); + var result = _projectCollection.UpdateOne(p => p.Id.Equals(projectId), update); + } + public void DeleteProjectFromProject(ObjectId rootProjectId, ObjectId deletedProjectId) + { + var list = _projectCollection.AsQueryable().FirstOrDefault(p => p.Id.Equals(rootProjectId)).Projects; + list.Remove(deletedProjectId); + var update = Builders.Update + .Set("Projects", list) + .CurrentDate("lastModified"); + var result = _projectCollection.UpdateOne(p => p.Id.Equals(rootProjectId), update); } public void DeleteImageFromProject(ObjectId projectId, ObjectId imageId) { - var project = _projectCollection.AsQueryable().FirstOrDefault(p => p.Id.Equals(projectId)); - _projectCollection.Remove(Query.EQ("_id", projectId)); - project.Images.Remove(imageId); - _projectCollection.Insert(project); + var list = _projectCollection.AsQueryable().FirstOrDefault(p => p.Id.Equals(projectId)).Images; + list.Remove(imageId); + var update = Builders.Update + .Set("Images", list) + .CurrentDate("lastModified"); + var result = _projectCollection.UpdateOne(p => p.Id.Equals(projectId), update); } public Project AddProject(Project project) { - _projectCollection.Insert(project); - return project; + _projectCollection.InsertOne(project); + return project; } public List GetAllProject() { //ToDo delete this bad method - return _projectCollection.FindAll().ToList(); + return _projectCollection.AsQueryable().ToList(); } public Project GetProjectById(ObjectId id) @@ -61,47 +74,33 @@ public Project GetProjectById(ObjectId id) return _projectCollection.AsQueryable().FirstOrDefault(p => p.Id.Equals(id)); } - public void AddProjectToProject(ObjectId newProject, ObjectId iDRootProject) + public void AddProjectToProject(ObjectId newProject, ObjectId idRootProject) { - var project = _projectCollection.AsQueryable().FirstOrDefault(im => im.Id.Equals(iDRootProject)); - _projectCollection.Remove(Query.EQ("_id", iDRootProject)); - project.Comments.Add(newProject); - _projectCollection.Insert(project); + var update = Builders.Update + .AddToSet("Projects", newProject) + .CurrentDate("lastModified"); + var result = _projectCollection.UpdateOne(p => p.Id.Equals(idRootProject), update); } - public void AddImageToProject(ObjectId newImage, ObjectId iDProject) + public void AddImageToProject(ObjectId newImage, ObjectId idProject) { - var project = _projectCollection.AsQueryable().FirstOrDefault(im => im.Id.Equals(iDProject)); - _projectCollection.Remove(Query.EQ("_id", iDProject)); - project.Comments.Add(newImage); - _projectCollection.Insert(project); + var update = Builders.Update + .AddToSet("Images", newImage) + .CurrentDate("lastModified"); + var result = _projectCollection.UpdateOne(p => p.Id.Equals(idProject), update); } - public void AddCommentToProject(ObjectId newComment, ObjectId iDProject) + public void AddCommentToProject(ObjectId newComment, ObjectId idProject) { - var project = _projectCollection.AsQueryable().FirstOrDefault(im => im.Id.Equals(iDProject)); - _projectCollection.Remove(Query.EQ("_id", iDProject)); - project.Comments.Add(newComment); - _projectCollection.Insert(project); + var update = Builders.Update + .AddToSet("Comments", newComment) + .CurrentDate("lastModified"); + var result = _projectCollection.UpdateOne(p => p.Id.Equals(idProject), update); } public List GetProjectsByIds(List ids) { - var list = _projectCollection.FindAll().ToList(); - HashSet id = new HashSet(); - foreach (ObjectId i in ids) - { - id.Add(i); - } - var projects = new List(); - foreach (Project p in list) - { - if (id.Contains(p.Id)) - { - projects.Add(p); - } - } - return projects; + return _projectCollection.AsQueryable().Where(p => ids.Contains(p.Id)).ToList(); } } } diff --git a/Ease-L.sln b/Ease-L.sln index 567f240..88e077c 100644 --- a/Ease-L.sln +++ b/Ease-L.sln @@ -7,6 +7,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebApp", "WebApp\WebApp.csp EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DB", "DB\DB.csproj", "{4841AB80-72FD-4F53-BB68-28B111786669}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ControllersLogic", "ControllersLogic\ControllersLogic.csproj", "{89FC8B31-B22A-40DD-981E-89A86D96BE34}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -21,6 +23,10 @@ Global {4841AB80-72FD-4F53-BB68-28B111786669}.Debug|Any CPU.Build.0 = Debug|Any CPU {4841AB80-72FD-4F53-BB68-28B111786669}.Release|Any CPU.ActiveCfg = Release|Any CPU {4841AB80-72FD-4F53-BB68-28B111786669}.Release|Any CPU.Build.0 = Release|Any CPU + {89FC8B31-B22A-40DD-981E-89A86D96BE34}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {89FC8B31-B22A-40DD-981E-89A86D96BE34}.Debug|Any CPU.Build.0 = Debug|Any CPU + {89FC8B31-B22A-40DD-981E-89A86D96BE34}.Release|Any CPU.ActiveCfg = Release|Any CPU + {89FC8B31-B22A-40DD-981E-89A86D96BE34}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/WebApp/App_Data/XmlDocument.xml b/WebApp/App_Data/XmlDocument.xml new file mode 100644 index 0000000..2ebb9ce --- /dev/null +++ b/WebApp/App_Data/XmlDocument.xml @@ -0,0 +1,682 @@ + + + + WebApp + + + + + Generates an URI-friendly ID for the . E.g. "Get-Values-id_name" instead of "GetValues/{id}?name={name}" + + The . + The ID as a string. + + + + Use this class to customize the Help Page. + For example you can set a custom to supply the documentation + or you can provide the samples for the requests/responses. + + + + + The controller that will handle requests for the help page. + + + + + Sets the documentation provider for help page. + + The . + The documentation provider. + + + + Sets the objects that will be used by the formatters to produce sample requests/responses. + + The . + The sample objects. + + + + Sets the sample request directly for the specified media type and action. + + The . + The sample request. + The media type. + Name of the controller. + Name of the action. + + + + Sets the sample request directly for the specified media type and action with parameters. + + The . + The sample request. + The media type. + Name of the controller. + Name of the action. + The parameter names. + + + + Sets the sample request directly for the specified media type of the action. + + The . + The sample response. + The media type. + Name of the controller. + Name of the action. + + + + Sets the sample response directly for the specified media type of the action with specific parameters. + + The . + The sample response. + The media type. + Name of the controller. + Name of the action. + The parameter names. + + + + Sets the sample directly for all actions with the specified media type. + + The . + The sample. + The media type. + + + + Sets the sample directly for all actions with the specified type and media type. + + The . + The sample. + The media type. + The parameter type or return type of an action. + + + + Specifies the actual type of passed to the in an action. + The help page will use this information to produce more accurate request samples. + + The . + The type. + Name of the controller. + Name of the action. + + + + Specifies the actual type of passed to the in an action. + The help page will use this information to produce more accurate request samples. + + The . + The type. + Name of the controller. + Name of the action. + The parameter names. + + + + Specifies the actual type of returned as part of the in an action. + The help page will use this information to produce more accurate response samples. + + The . + The type. + Name of the controller. + Name of the action. + + + + Specifies the actual type of returned as part of the in an action. + The help page will use this information to produce more accurate response samples. + + The . + The type. + Name of the controller. + Name of the action. + The parameter names. + + + + Gets the help page sample generator. + + The . + The help page sample generator. + + + + Sets the help page sample generator. + + The . + The help page sample generator. + + + + Gets the model description generator. + + The configuration. + The + + + + Gets the model that represents an API displayed on the help page. The model is initialized on the first call and cached for subsequent calls. + + The . + The ID. + + An + + + + + Describes a type model. + + + + + Generates model descriptions for given types. + + + + + Use this attribute to change the name of the generated for a type. + + + + + The model that represents an API displayed on the help page. + + + + + Initializes a new instance of the class. + + + + + Gets or sets the that describes the API. + + + + + Gets or sets the collection that describes the URI parameters for the API. + + + + + Gets or sets the documentation for the request. + + + + + Gets or sets the that describes the request body. + + + + + Gets the request body parameter descriptions. + + + + + Gets or sets the that describes the resource. + + + + + Gets the resource property descriptions. + + + + + Gets the sample requests associated with the API. + + + + + Gets the sample responses associated with the API. + + + + + Gets the error messages associated with this model. + + + + + This class will generate the samples for the help page. + + + + + Initializes a new instance of the class. + + + + + Gets CLR types that are used as the content of or . + + + + + Gets the objects that are used directly as samples for certain actions. + + + + + Gets the objects that are serialized as samples by the supported formatters. + + + + + Gets factories for the objects that the supported formatters will serialize as samples. Processed in order, + stopping when the factory successfully returns a non- object. + + + Collection includes just initially. Use + SampleObjectFactories.Insert(0, func) to provide an override and + SampleObjectFactories.Add(func) to provide a fallback. + + + + Gets the request body samples for a given . + + The . + The samples keyed by media type. + + + + Gets the response body samples for a given . + + The . + The samples keyed by media type. + + + + Gets the request or response body samples. + + The . + The value indicating whether the sample is for a request or for a response. + The samples keyed by media type. + + + + Search for samples that are provided directly through . + + Name of the controller. + Name of the action. + The parameter names. + The CLR type. + The formatter. + The media type. + The value indicating whether the sample is for a request or for a response. + The sample that matches the parameters. + + + + Gets the sample object that will be serialized by the formatters. + First, it will look at the . If no sample object is found, it will try to create + one using (which wraps an ) and other + factories in . + + The type. + The sample object. + + + + Resolves the actual type of passed to the in an action. + + The . + The type. + + + + Resolves the type of the action parameter or return value when or is used. + + The . + Name of the controller. + Name of the action. + The parameter names. + The value indicating whether the sample is for a request or a response. + The formatters. + + + + Writes the sample object using formatter. + + The formatter. + The value. + The type. + Type of the media. + + + + + This is used to identify the place where the sample should be applied. + + + + + Creates a new based on media type. + + The media type. + + + + Creates a new based on media type and CLR type. + + The media type. + The CLR type. + + + + Creates a new based on , controller name, action name and parameter names. + + The . + Name of the controller. + Name of the action. + The parameter names. + + + + Creates a new based on media type, , controller name, action name and parameter names. + + The media type. + The . + Name of the controller. + Name of the action. + The parameter names. + + + + Gets the name of the controller. + + + The name of the controller. + + + + + Gets the name of the action. + + + The name of the action. + + + + + Gets the media type. + + + The media type. + + + + + Gets the parameter names. + + + + + Gets the . + + + + + This represents an image sample on the help page. There's a display template named ImageSample associated with this class. + + + + + Initializes a new instance of the class. + + The URL of an image. + + + + This represents an invalid sample on the help page. There's a display template named InvalidSample associated with this class. + + + + + This class will create an object of a given type and populate it with sample data. + + + + + Generates an object for a given type. The type needs to be public, have a public default constructor and settable public properties/fields. Currently it supports the following types: + Simple types: , , , , , etc. + Complex types: POCO types. + Nullables: . + Arrays: arrays of simple types or complex types. + Key value pairs: + Tuples: , , etc + Dictionaries: or anything deriving from . + Collections: , , , , , or anything deriving from or . + Queryables: , . + + The type. + An object of the given type. + + + + Indicates whether the sample is used for request or response + + + + + This represents a preformatted text sample on the help page. There's a display template named TextSample associated with this class. + + + + + A custom that reads the API documentation from an XML documentation file. + + + + + Initializes a new instance of the class. + + The physical path to XML document. + + + + Make new comment. + + Text + Name + Return id new comment + + + + Get all comments + + all comments + + + + Get comment by id + + Id + + + + + Update comment by id + + Id + New name + New text + New Comment + + + + Add comment to image + + Image id + Comment text + Comment name + Id new comment + + + + Make new image + + Image url + Name + Id new image + + + + Delete comment from image + + Imgae id + Comment id + Result + + + + Download image for getting url + + Uploded image, HttpPostedFileBase + Image url + + + + Get all image + + List image + + + + Get image by id + + Id + Image + + + + Get image by id and version + + Id + Version + Image + + + + Get comments from image + + Image id + List comments + + + + Update image + + Id + New name + New url + New Image + + + + Add new comment to project + + Text + Name + Project id + Id new project + + + + Add new image to ptoject + + Image url + Name + Project id + Id new image + + + + Add new project + + Name + Id new project + + + + Add new project into ptoject + + Id root project + Name new project + Id new project + + + + Delete project by id + + Deleted project id + Result + + + + GetAllProject + + List project + + + + Delete comment from project + + Project id + Comment id + Result + + + + Delete image from project + + Project id + Image id + Result + + + + Get project by id + + Id + Project + + + + Get comment from project + + Id project + List id comments + + + + Get image from project + + Id project + List id images + + + + Get project from project + + Id root project + List id projects + + + + Update project by id + + Id + New name + New project + + + diff --git a/WebApp/App_Start/ApplicationIdentityContext.cs b/WebApp/App_Start/ApplicationIdentityContext.cs deleted file mode 100644 index 103b53e..0000000 --- a/WebApp/App_Start/ApplicationIdentityContext.cs +++ /dev/null @@ -1,42 +0,0 @@ -using DB; -using Microsoft.AspNet.Identity.EntityFramework; -using MongoDB.Driver; -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using System.Web; -using WebApp.Models; - -namespace WebApp.App_Start -{ - public class ApplicationIdentityContext : IDisposable - { - /*public static ApplicationIdentityContext Create() - { - // todo add settings where appropriate to switch server & database in your own application - var database = MongoClientFactory.GetMongoDatabase(); - var users = database.GetCollection("users"); - var roles = database.GetCollection("roles"); - return new ApplicationIdentityContext(users, roles); - }*/ - - private ApplicationIdentityContext(IMongoCollection users, IMongoCollection roles) - { - Users = users; - Roles = roles; - } - - public IMongoCollection Roles { get; set; } - - public IMongoCollection Users { get; set; } - - public Task> AllRolesAsync() - { - return Roles.Find(r => true).ToListAsync(); - } - - public void Dispose() - { - } - } -} \ No newline at end of file diff --git a/WebApp/App_Start/IdentityConfig.cs b/WebApp/App_Start/IdentityConfig.cs index 0da69bd..c50ece0 100644 --- a/WebApp/App_Start/IdentityConfig.cs +++ b/WebApp/App_Start/IdentityConfig.cs @@ -11,7 +11,6 @@ using Microsoft.Owin; using Microsoft.Owin.Security; using WebApp.Models; -using WebApp.App_Start; namespace WebApp { @@ -87,54 +86,6 @@ public static ApplicationUserManager Create(IdentityFactoryOptions AddUserToRolesAsync(string userId, IList roles) - { - var userRoleStore = (IUserRoleStore)Store; - - var user = await FindByIdAsync(userId).ConfigureAwait(false); - if (user == null) - { - throw new InvalidOperationException("Invalid user Id"); - } - - var userRoles = await userRoleStore.GetRolesAsync(user).ConfigureAwait(false); - // Add user to each role using UserRoleStore - foreach (var role in roles.Where(role => !userRoles.Contains(role))) - { - await userRoleStore.AddToRoleAsync(user, role).ConfigureAwait(false); - } - - // Call update once when all roles are added - return await UpdateAsync(user).ConfigureAwait(false); - } - - /// - /// Remove user from multiple roles - /// - /// user id - /// list of role names - /// - public virtual async Task RemoveUserFromRolesAsync(string userId, IList roles) - { - var userRoleStore = (IUserRoleStore)Store; - - var user = await FindByIdAsync(userId).ConfigureAwait(false); - if (user == null) - { - throw new InvalidOperationException("Invalid user Id"); - } - - var userRoles = await userRoleStore.GetRolesAsync(user).ConfigureAwait(false); - // Remove user to each role using UserRoleStore - foreach (var role in roles.Where(userRoles.Contains)) - { - await userRoleStore.RemoveFromRoleAsync(user, role).ConfigureAwait(false); - } - - // Call update once when all roles are removed - return await UpdateAsync(user).ConfigureAwait(false); - } } // Configure the application sign-in manager which is used in this application. diff --git a/WebApp/App_Start/RouteConfig.cs b/WebApp/App_Start/RouteConfig.cs index 3c9ac3b..431d5a3 100644 --- a/WebApp/App_Start/RouteConfig.cs +++ b/WebApp/App_Start/RouteConfig.cs @@ -4,6 +4,7 @@ using System.Web; using System.Web.Mvc; using System.Web.Routing; +using WebApp.Areas.HelpPage; namespace WebApp { @@ -14,7 +15,6 @@ public static void RegisterRoutes(RouteCollection routes) routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); routes.MapMvcAttributeRoutes(); - routes.MapRoute( name: "Default", url: "{controller}/{action}/{id}", diff --git a/WebApp/App_Start/WebApiConfig.cs b/WebApp/App_Start/WebApiConfig.cs new file mode 100644 index 0000000..c451248 --- /dev/null +++ b/WebApp/App_Start/WebApiConfig.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http.Headers; +using System.Web; +using System.Web.Http; +using WebApp.Areas.HelpPage; + +namespace WebApp.App_Start +{ + public static class WebApiConfig + { + public static void Register(HttpConfiguration config) + { + // Web API configuration and services + + // Web API routes + config.MapHttpAttributeRoutes(); + config.Formatters.JsonFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/html")); + config.Routes.MapHttpRoute( + name: "DefaultApi", + routeTemplate: "api/{controller}/{id}", + defaults: new { id = RouteParameter.Optional } + ); + } + } +} \ No newline at end of file diff --git a/WebApp/Areas/HelpPage/ApiDescriptionExtensions.cs b/WebApp/Areas/HelpPage/ApiDescriptionExtensions.cs new file mode 100644 index 0000000..a6461f0 --- /dev/null +++ b/WebApp/Areas/HelpPage/ApiDescriptionExtensions.cs @@ -0,0 +1,39 @@ +using System; +using System.Text; +using System.Web; +using System.Web.Http.Description; + +namespace WebApp.Areas.HelpPage +{ + public static class ApiDescriptionExtensions + { + /// + /// Generates an URI-friendly ID for the . E.g. "Get-Values-id_name" instead of "GetValues/{id}?name={name}" + /// + /// The . + /// The ID as a string. + public static string GetFriendlyId(this ApiDescription description) + { + string path = description.RelativePath; + string[] urlParts = path.Split('?'); + string localPath = urlParts[0]; + string queryKeyString = null; + if (urlParts.Length > 1) + { + string query = urlParts[1]; + string[] queryKeys = HttpUtility.ParseQueryString(query).AllKeys; + queryKeyString = String.Join("_", queryKeys); + } + + StringBuilder friendlyPath = new StringBuilder(); + friendlyPath.AppendFormat("{0}-{1}", + description.HttpMethod.Method, + localPath.Replace("/", "-").Replace("{", String.Empty).Replace("}", String.Empty)); + if (queryKeyString != null) + { + friendlyPath.AppendFormat("_{0}", queryKeyString.Replace('.', '-')); + } + return friendlyPath.ToString(); + } + } +} \ No newline at end of file diff --git a/WebApp/Areas/HelpPage/App_Start/HelpPageConfig.cs b/WebApp/Areas/HelpPage/App_Start/HelpPageConfig.cs new file mode 100644 index 0000000..841188a --- /dev/null +++ b/WebApp/Areas/HelpPage/App_Start/HelpPageConfig.cs @@ -0,0 +1,114 @@ +// Uncomment the following to provide samples for PageResult. Must also add the Microsoft.AspNet.WebApi.OData +// package to your project. +////#define Handle_PageResultOfT + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Net.Http.Headers; +using System.Reflection; +using System.Web; +using System.Web.Http; +#if Handle_PageResultOfT +using System.Web.Http.OData; +#endif + +namespace WebApp.Areas.HelpPage +{ + /// + /// Use this class to customize the Help Page. + /// For example you can set a custom to supply the documentation + /// or you can provide the samples for the requests/responses. + /// + public static class HelpPageConfig + { + [SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", + MessageId = "WebApp.Areas.HelpPage.TextSample.#ctor(System.String)", + Justification = "End users may choose to merge this string with existing localized resources.")] + [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", + MessageId = "bsonspec", + Justification = "Part of a URI.")] + public static void Register(HttpConfiguration config) + { + //// Uncomment the following to use the documentation from XML documentation file. + //config.SetDocumentationProvider(new XmlDocumentationProvider(HttpContext.Current.Server.MapPath("~/App_Data/XmlDocument.xml"))); + + //// Uncomment the following to use "sample string" as the sample for all actions that have string as the body parameter or return type. + //// Also, the string arrays will be used for IEnumerable. The sample objects will be serialized into different media type + //// formats by the available formatters. + //config.SetSampleObjects(new Dictionary + //{ + // {typeof(string), "sample string"}, + // {typeof(IEnumerable), new string[]{"sample 1", "sample 2"}} + //}); + + // Extend the following to provide factories for types not handled automatically (those lacking parameterless + // constructors) or for which you prefer to use non-default property values. Line below provides a fallback + // since automatic handling will fail and GeneratePageResult handles only a single type. +#if Handle_PageResultOfT + config.GetHelpPageSampleGenerator().SampleObjectFactories.Add(GeneratePageResult); +#endif + + // Extend the following to use a preset object directly as the sample for all actions that support a media + // type, regardless of the body parameter or return type. The lines below avoid display of binary content. + // The BsonMediaTypeFormatter (if available) is not used to serialize the TextSample object. + config.SetDocumentationProvider(new XmlDocumentationProvider(HttpContext.Current.Server.MapPath("~/App_Data/XmlDocument.xml"))); + config.SetSampleForMediaType( + new TextSample("Binary JSON content. See http://bsonspec.org for details."), + new MediaTypeHeaderValue("application/bson")); + + //// Uncomment the following to use "[0]=foo&[1]=bar" directly as the sample for all actions that support form URL encoded format + //// and have IEnumerable as the body parameter or return type. + //config.SetSampleForType("[0]=foo&[1]=bar", new MediaTypeHeaderValue("application/x-www-form-urlencoded"), typeof(IEnumerable)); + + //// Uncomment the following to use "1234" directly as the request sample for media type "text/plain" on the controller named "Values" + //// and action named "Put". + //config.SetSampleRequest("1234", new MediaTypeHeaderValue("text/plain"), "Values", "Put"); + + //// Uncomment the following to use the image on "../images/aspNetHome.png" directly as the response sample for media type "image/png" + //// on the controller named "Values" and action named "Get" with parameter "id". + //config.SetSampleResponse(new ImageSample("../images/aspNetHome.png"), new MediaTypeHeaderValue("image/png"), "Values", "Get", "id"); + + //// Uncomment the following to correct the sample request when the action expects an HttpRequestMessage with ObjectContent. + //// The sample will be generated as if the controller named "Values" and action named "Get" were having string as the body parameter. + //config.SetActualRequestType(typeof(string), "Values", "Get"); + + //// Uncomment the following to correct the sample response when the action returns an HttpResponseMessage with ObjectContent. + //// The sample will be generated as if the controller named "Values" and action named "Post" were returning a string. + //config.SetActualResponseType(typeof(string), "Values", "Post"); + } + +#if Handle_PageResultOfT + private static object GeneratePageResult(HelpPageSampleGenerator sampleGenerator, Type type) + { + if (type.IsGenericType) + { + Type openGenericType = type.GetGenericTypeDefinition(); + if (openGenericType == typeof(PageResult<>)) + { + // Get the T in PageResult + Type[] typeParameters = type.GetGenericArguments(); + Debug.Assert(typeParameters.Length == 1); + + // Create an enumeration to pass as the first parameter to the PageResult constuctor + Type itemsType = typeof(List<>).MakeGenericType(typeParameters); + object items = sampleGenerator.GetSampleObject(itemsType); + + // Fill in the other information needed to invoke the PageResult constuctor + Type[] parameterTypes = new Type[] { itemsType, typeof(Uri), typeof(long?), }; + object[] parameters = new object[] { items, null, (long)ObjectGenerator.DefaultCollectionSize, }; + + // Call PageResult(IEnumerable items, Uri nextPageLink, long? count) constructor + ConstructorInfo constructor = type.GetConstructor(parameterTypes); + return constructor.Invoke(parameters); + } + } + + return null; + } +#endif + } +} \ No newline at end of file diff --git a/WebApp/Areas/HelpPage/Controllers/HelpController.cs b/WebApp/Areas/HelpPage/Controllers/HelpController.cs new file mode 100644 index 0000000..7795596 --- /dev/null +++ b/WebApp/Areas/HelpPage/Controllers/HelpController.cs @@ -0,0 +1,63 @@ +using System; +using System.Web.Http; +using System.Web.Mvc; +using WebApp.Areas.HelpPage.ModelDescriptions; +using WebApp.Areas.HelpPage.Models; + +namespace WebApp.Areas.HelpPage.Controllers +{ + /// + /// The controller that will handle requests for the help page. + /// + public class HelpController : Controller + { + private const string ErrorViewName = "Error"; + + public HelpController() + : this(GlobalConfiguration.Configuration) + { + } + + public HelpController(HttpConfiguration config) + { + Configuration = config; + } + + public HttpConfiguration Configuration { get; private set; } + + public ActionResult Index() + { + ViewBag.DocumentationProvider = Configuration.Services.GetDocumentationProvider(); + return View(Configuration.Services.GetApiExplorer().ApiDescriptions); + } + + public ActionResult Api(string apiId) + { + if (!String.IsNullOrEmpty(apiId)) + { + HelpPageApiModel apiModel = Configuration.GetHelpPageApiModel(apiId); + if (apiModel != null) + { + return View(apiModel); + } + } + + return View(ErrorViewName); + } + + public ActionResult ResourceModel(string modelName) + { + if (!String.IsNullOrEmpty(modelName)) + { + ModelDescriptionGenerator modelDescriptionGenerator = Configuration.GetModelDescriptionGenerator(); + ModelDescription modelDescription; + if (modelDescriptionGenerator.GeneratedModels.TryGetValue(modelName, out modelDescription)) + { + return View(modelDescription); + } + } + + return View(ErrorViewName); + } + } +} \ No newline at end of file diff --git a/WebApp/Areas/HelpPage/HelpPage.css b/WebApp/Areas/HelpPage/HelpPage.css new file mode 100644 index 0000000..aff2230 --- /dev/null +++ b/WebApp/Areas/HelpPage/HelpPage.css @@ -0,0 +1,134 @@ +.help-page h1, +.help-page .h1, +.help-page h2, +.help-page .h2, +.help-page h3, +.help-page .h3, +#body.help-page, +.help-page-table th, +.help-page-table pre, +.help-page-table p { + font-family: "Segoe UI Light", Frutiger, "Frutiger Linotype", "Dejavu Sans", "Helvetica Neue", Arial, sans-serif; +} + +.help-page pre.wrapped { + white-space: -moz-pre-wrap; + white-space: -pre-wrap; + white-space: -o-pre-wrap; + white-space: pre-wrap; +} + +.help-page .warning-message-container { + margin-top: 20px; + padding: 0 10px; + color: #525252; + background: #EFDCA9; + border: 1px solid #CCCCCC; +} + +.help-page-table { + width: 100%; + border-collapse: collapse; + text-align: left; + margin: 0px 0px 20px 0px; + border-top: 1px solid #D4D4D4; +} + +.help-page-table th { + text-align: left; + font-weight: bold; + border-bottom: 1px solid #D4D4D4; + padding: 5px 6px 5px 6px; +} + +.help-page-table td { + border-bottom: 1px solid #D4D4D4; + padding: 10px 8px 10px 8px; + vertical-align: top; +} + +.help-page-table pre, +.help-page-table p { + margin: 0px; + padding: 0px; + font-family: inherit; + font-size: 100%; +} + +.help-page-table tbody tr:hover td { + background-color: #F3F3F3; +} + +.help-page a:hover { + background-color: transparent; +} + +.help-page .sample-header { + border: 2px solid #D4D4D4; + background: #00497E; + color: #FFFFFF; + padding: 8px 15px; + border-bottom: none; + display: inline-block; + margin: 10px 0px 0px 0px; +} + +.help-page .sample-content { + display: block; + border-width: 0; + padding: 15px 20px; + background: #FFFFFF; + border: 2px solid #D4D4D4; + margin: 0px 0px 10px 0px; +} + +.help-page .api-name { + width: 40%; +} + +.help-page .api-documentation { + width: 60%; +} + +.help-page .parameter-name { + width: 20%; +} + +.help-page .parameter-documentation { + width: 40%; +} + +.help-page .parameter-type { + width: 20%; +} + +.help-page .parameter-annotations { + width: 20%; +} + +.help-page h1, +.help-page .h1 { + font-size: 36px; + line-height: normal; +} + +.help-page h2, +.help-page .h2 { + font-size: 24px; +} + +.help-page h3, +.help-page .h3 { + font-size: 20px; +} + +#body.help-page { + font-size: 14px; + line-height: 143%; + color: #333; +} + +.help-page a { + color: #0000EE; + text-decoration: none; +} diff --git a/WebApp/Areas/HelpPage/HelpPageAreaRegistration.cs b/WebApp/Areas/HelpPage/HelpPageAreaRegistration.cs new file mode 100644 index 0000000..d0405c7 --- /dev/null +++ b/WebApp/Areas/HelpPage/HelpPageAreaRegistration.cs @@ -0,0 +1,26 @@ +using System.Web.Http; +using System.Web.Mvc; + +namespace WebApp.Areas.HelpPage +{ + public class HelpPageAreaRegistration : AreaRegistration + { + public override string AreaName + { + get + { + return "HelpPage"; + } + } + + public override void RegisterArea(AreaRegistrationContext context) + { + context.MapRoute( + "HelpPage_Default", + "Help/{action}/{apiId}", + new { controller = "Help", action = "Index", apiId = UrlParameter.Optional }); + + HelpPageConfig.Register(GlobalConfiguration.Configuration); + } + } +} \ No newline at end of file diff --git a/WebApp/Areas/HelpPage/HelpPageConfigurationExtensions.cs b/WebApp/Areas/HelpPage/HelpPageConfigurationExtensions.cs new file mode 100644 index 0000000..ffd881b --- /dev/null +++ b/WebApp/Areas/HelpPage/HelpPageConfigurationExtensions.cs @@ -0,0 +1,467 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Linq; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Web.Http; +using System.Web.Http.Controllers; +using System.Web.Http.Description; +using WebApp.Areas.HelpPage.ModelDescriptions; +using WebApp.Areas.HelpPage.Models; + +namespace WebApp.Areas.HelpPage +{ + public static class HelpPageConfigurationExtensions + { + private const string ApiModelPrefix = "MS_HelpPageApiModel_"; + + /// + /// Sets the documentation provider for help page. + /// + /// The . + /// The documentation provider. + public static void SetDocumentationProvider(this HttpConfiguration config, IDocumentationProvider documentationProvider) + { + config.Services.Replace(typeof(IDocumentationProvider), documentationProvider); + } + + /// + /// Sets the objects that will be used by the formatters to produce sample requests/responses. + /// + /// The . + /// The sample objects. + public static void SetSampleObjects(this HttpConfiguration config, IDictionary sampleObjects) + { + config.GetHelpPageSampleGenerator().SampleObjects = sampleObjects; + } + + /// + /// Sets the sample request directly for the specified media type and action. + /// + /// The . + /// The sample request. + /// The media type. + /// Name of the controller. + /// Name of the action. + public static void SetSampleRequest(this HttpConfiguration config, object sample, MediaTypeHeaderValue mediaType, string controllerName, string actionName) + { + config.GetHelpPageSampleGenerator().ActionSamples.Add(new HelpPageSampleKey(mediaType, SampleDirection.Request, controllerName, actionName, new[] { "*" }), sample); + } + + /// + /// Sets the sample request directly for the specified media type and action with parameters. + /// + /// The . + /// The sample request. + /// The media type. + /// Name of the controller. + /// Name of the action. + /// The parameter names. + public static void SetSampleRequest(this HttpConfiguration config, object sample, MediaTypeHeaderValue mediaType, string controllerName, string actionName, params string[] parameterNames) + { + config.GetHelpPageSampleGenerator().ActionSamples.Add(new HelpPageSampleKey(mediaType, SampleDirection.Request, controllerName, actionName, parameterNames), sample); + } + + /// + /// Sets the sample request directly for the specified media type of the action. + /// + /// The . + /// The sample response. + /// The media type. + /// Name of the controller. + /// Name of the action. + public static void SetSampleResponse(this HttpConfiguration config, object sample, MediaTypeHeaderValue mediaType, string controllerName, string actionName) + { + config.GetHelpPageSampleGenerator().ActionSamples.Add(new HelpPageSampleKey(mediaType, SampleDirection.Response, controllerName, actionName, new[] { "*" }), sample); + } + + /// + /// Sets the sample response directly for the specified media type of the action with specific parameters. + /// + /// The . + /// The sample response. + /// The media type. + /// Name of the controller. + /// Name of the action. + /// The parameter names. + public static void SetSampleResponse(this HttpConfiguration config, object sample, MediaTypeHeaderValue mediaType, string controllerName, string actionName, params string[] parameterNames) + { + config.GetHelpPageSampleGenerator().ActionSamples.Add(new HelpPageSampleKey(mediaType, SampleDirection.Response, controllerName, actionName, parameterNames), sample); + } + + /// + /// Sets the sample directly for all actions with the specified media type. + /// + /// The . + /// The sample. + /// The media type. + public static void SetSampleForMediaType(this HttpConfiguration config, object sample, MediaTypeHeaderValue mediaType) + { + config.GetHelpPageSampleGenerator().ActionSamples.Add(new HelpPageSampleKey(mediaType), sample); + } + + /// + /// Sets the sample directly for all actions with the specified type and media type. + /// + /// The . + /// The sample. + /// The media type. + /// The parameter type or return type of an action. + public static void SetSampleForType(this HttpConfiguration config, object sample, MediaTypeHeaderValue mediaType, Type type) + { + config.GetHelpPageSampleGenerator().ActionSamples.Add(new HelpPageSampleKey(mediaType, type), sample); + } + + /// + /// Specifies the actual type of passed to the in an action. + /// The help page will use this information to produce more accurate request samples. + /// + /// The . + /// The type. + /// Name of the controller. + /// Name of the action. + public static void SetActualRequestType(this HttpConfiguration config, Type type, string controllerName, string actionName) + { + config.GetHelpPageSampleGenerator().ActualHttpMessageTypes.Add(new HelpPageSampleKey(SampleDirection.Request, controllerName, actionName, new[] { "*" }), type); + } + + /// + /// Specifies the actual type of passed to the in an action. + /// The help page will use this information to produce more accurate request samples. + /// + /// The . + /// The type. + /// Name of the controller. + /// Name of the action. + /// The parameter names. + public static void SetActualRequestType(this HttpConfiguration config, Type type, string controllerName, string actionName, params string[] parameterNames) + { + config.GetHelpPageSampleGenerator().ActualHttpMessageTypes.Add(new HelpPageSampleKey(SampleDirection.Request, controllerName, actionName, parameterNames), type); + } + + /// + /// Specifies the actual type of returned as part of the in an action. + /// The help page will use this information to produce more accurate response samples. + /// + /// The . + /// The type. + /// Name of the controller. + /// Name of the action. + public static void SetActualResponseType(this HttpConfiguration config, Type type, string controllerName, string actionName) + { + config.GetHelpPageSampleGenerator().ActualHttpMessageTypes.Add(new HelpPageSampleKey(SampleDirection.Response, controllerName, actionName, new[] { "*" }), type); + } + + /// + /// Specifies the actual type of returned as part of the in an action. + /// The help page will use this information to produce more accurate response samples. + /// + /// The . + /// The type. + /// Name of the controller. + /// Name of the action. + /// The parameter names. + public static void SetActualResponseType(this HttpConfiguration config, Type type, string controllerName, string actionName, params string[] parameterNames) + { + config.GetHelpPageSampleGenerator().ActualHttpMessageTypes.Add(new HelpPageSampleKey(SampleDirection.Response, controllerName, actionName, parameterNames), type); + } + + /// + /// Gets the help page sample generator. + /// + /// The . + /// The help page sample generator. + public static HelpPageSampleGenerator GetHelpPageSampleGenerator(this HttpConfiguration config) + { + return (HelpPageSampleGenerator)config.Properties.GetOrAdd( + typeof(HelpPageSampleGenerator), + k => new HelpPageSampleGenerator()); + } + + /// + /// Sets the help page sample generator. + /// + /// The . + /// The help page sample generator. + public static void SetHelpPageSampleGenerator(this HttpConfiguration config, HelpPageSampleGenerator sampleGenerator) + { + config.Properties.AddOrUpdate( + typeof(HelpPageSampleGenerator), + k => sampleGenerator, + (k, o) => sampleGenerator); + } + + /// + /// Gets the model description generator. + /// + /// The configuration. + /// The + public static ModelDescriptionGenerator GetModelDescriptionGenerator(this HttpConfiguration config) + { + return (ModelDescriptionGenerator)config.Properties.GetOrAdd( + typeof(ModelDescriptionGenerator), + k => InitializeModelDescriptionGenerator(config)); + } + + /// + /// Gets the model that represents an API displayed on the help page. The model is initialized on the first call and cached for subsequent calls. + /// + /// The . + /// The ID. + /// + /// An + /// + public static HelpPageApiModel GetHelpPageApiModel(this HttpConfiguration config, string apiDescriptionId) + { + object model; + string modelId = ApiModelPrefix + apiDescriptionId; + if (!config.Properties.TryGetValue(modelId, out model)) + { + Collection apiDescriptions = config.Services.GetApiExplorer().ApiDescriptions; + ApiDescription apiDescription = apiDescriptions.FirstOrDefault(api => String.Equals(api.GetFriendlyId(), apiDescriptionId, StringComparison.OrdinalIgnoreCase)); + if (apiDescription != null) + { + model = GenerateApiModel(apiDescription, config); + config.Properties.TryAdd(modelId, model); + } + } + + return (HelpPageApiModel)model; + } + + private static HelpPageApiModel GenerateApiModel(ApiDescription apiDescription, HttpConfiguration config) + { + HelpPageApiModel apiModel = new HelpPageApiModel() + { + ApiDescription = apiDescription, + }; + + ModelDescriptionGenerator modelGenerator = config.GetModelDescriptionGenerator(); + HelpPageSampleGenerator sampleGenerator = config.GetHelpPageSampleGenerator(); + GenerateUriParameters(apiModel, modelGenerator); + GenerateRequestModelDescription(apiModel, modelGenerator, sampleGenerator); + GenerateResourceDescription(apiModel, modelGenerator); + GenerateSamples(apiModel, sampleGenerator); + + return apiModel; + } + + private static void GenerateUriParameters(HelpPageApiModel apiModel, ModelDescriptionGenerator modelGenerator) + { + ApiDescription apiDescription = apiModel.ApiDescription; + foreach (ApiParameterDescription apiParameter in apiDescription.ParameterDescriptions) + { + if (apiParameter.Source == ApiParameterSource.FromUri) + { + HttpParameterDescriptor parameterDescriptor = apiParameter.ParameterDescriptor; + Type parameterType = null; + ModelDescription typeDescription = null; + ComplexTypeModelDescription complexTypeDescription = null; + if (parameterDescriptor != null) + { + parameterType = parameterDescriptor.ParameterType; + typeDescription = modelGenerator.GetOrCreateModelDescription(parameterType); + complexTypeDescription = typeDescription as ComplexTypeModelDescription; + } + + // Example: + // [TypeConverter(typeof(PointConverter))] + // public class Point + // { + // public Point(int x, int y) + // { + // X = x; + // Y = y; + // } + // public int X { get; set; } + // public int Y { get; set; } + // } + // Class Point is bindable with a TypeConverter, so Point will be added to UriParameters collection. + // + // public class Point + // { + // public int X { get; set; } + // public int Y { get; set; } + // } + // Regular complex class Point will have properties X and Y added to UriParameters collection. + if (complexTypeDescription != null + && !IsBindableWithTypeConverter(parameterType)) + { + foreach (ParameterDescription uriParameter in complexTypeDescription.Properties) + { + apiModel.UriParameters.Add(uriParameter); + } + } + else if (parameterDescriptor != null) + { + ParameterDescription uriParameter = + AddParameterDescription(apiModel, apiParameter, typeDescription); + + if (!parameterDescriptor.IsOptional) + { + uriParameter.Annotations.Add(new ParameterAnnotation() { Documentation = "Required" }); + } + + object defaultValue = parameterDescriptor.DefaultValue; + if (defaultValue != null) + { + uriParameter.Annotations.Add(new ParameterAnnotation() { Documentation = "Default value is " + Convert.ToString(defaultValue, CultureInfo.InvariantCulture) }); + } + } + else + { + Debug.Assert(parameterDescriptor == null); + + // If parameterDescriptor is null, this is an undeclared route parameter which only occurs + // when source is FromUri. Ignored in request model and among resource parameters but listed + // as a simple string here. + ModelDescription modelDescription = modelGenerator.GetOrCreateModelDescription(typeof(string)); + AddParameterDescription(apiModel, apiParameter, modelDescription); + } + } + } + } + + private static bool IsBindableWithTypeConverter(Type parameterType) + { + if (parameterType == null) + { + return false; + } + + return TypeDescriptor.GetConverter(parameterType).CanConvertFrom(typeof(string)); + } + + private static ParameterDescription AddParameterDescription(HelpPageApiModel apiModel, + ApiParameterDescription apiParameter, ModelDescription typeDescription) + { + ParameterDescription parameterDescription = new ParameterDescription + { + Name = apiParameter.Name, + Documentation = apiParameter.Documentation, + TypeDescription = typeDescription, + }; + + apiModel.UriParameters.Add(parameterDescription); + return parameterDescription; + } + + private static void GenerateRequestModelDescription(HelpPageApiModel apiModel, ModelDescriptionGenerator modelGenerator, HelpPageSampleGenerator sampleGenerator) + { + ApiDescription apiDescription = apiModel.ApiDescription; + foreach (ApiParameterDescription apiParameter in apiDescription.ParameterDescriptions) + { + if (apiParameter.Source == ApiParameterSource.FromBody) + { + Type parameterType = apiParameter.ParameterDescriptor.ParameterType; + apiModel.RequestModelDescription = modelGenerator.GetOrCreateModelDescription(parameterType); + apiModel.RequestDocumentation = apiParameter.Documentation; + } + else if (apiParameter.ParameterDescriptor != null && + apiParameter.ParameterDescriptor.ParameterType == typeof(HttpRequestMessage)) + { + Type parameterType = sampleGenerator.ResolveHttpRequestMessageType(apiDescription); + + if (parameterType != null) + { + apiModel.RequestModelDescription = modelGenerator.GetOrCreateModelDescription(parameterType); + } + } + } + } + + private static void GenerateResourceDescription(HelpPageApiModel apiModel, ModelDescriptionGenerator modelGenerator) + { + ResponseDescription response = apiModel.ApiDescription.ResponseDescription; + Type responseType = response.ResponseType ?? response.DeclaredType; + if (responseType != null && responseType != typeof(void)) + { + apiModel.ResourceDescription = modelGenerator.GetOrCreateModelDescription(responseType); + } + } + + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "The exception is recorded as ErrorMessages.")] + private static void GenerateSamples(HelpPageApiModel apiModel, HelpPageSampleGenerator sampleGenerator) + { + try + { + foreach (var item in sampleGenerator.GetSampleRequests(apiModel.ApiDescription)) + { + apiModel.SampleRequests.Add(item.Key, item.Value); + LogInvalidSampleAsError(apiModel, item.Value); + } + + foreach (var item in sampleGenerator.GetSampleResponses(apiModel.ApiDescription)) + { + apiModel.SampleResponses.Add(item.Key, item.Value); + LogInvalidSampleAsError(apiModel, item.Value); + } + } + catch (Exception e) + { + apiModel.ErrorMessages.Add(String.Format(CultureInfo.CurrentCulture, + "An exception has occurred while generating the sample. Exception message: {0}", + HelpPageSampleGenerator.UnwrapException(e).Message)); + } + } + + private static bool TryGetResourceParameter(ApiDescription apiDescription, HttpConfiguration config, out ApiParameterDescription parameterDescription, out Type resourceType) + { + parameterDescription = apiDescription.ParameterDescriptions.FirstOrDefault( + p => p.Source == ApiParameterSource.FromBody || + (p.ParameterDescriptor != null && p.ParameterDescriptor.ParameterType == typeof(HttpRequestMessage))); + + if (parameterDescription == null) + { + resourceType = null; + return false; + } + + resourceType = parameterDescription.ParameterDescriptor.ParameterType; + + if (resourceType == typeof(HttpRequestMessage)) + { + HelpPageSampleGenerator sampleGenerator = config.GetHelpPageSampleGenerator(); + resourceType = sampleGenerator.ResolveHttpRequestMessageType(apiDescription); + } + + if (resourceType == null) + { + parameterDescription = null; + return false; + } + + return true; + } + + private static ModelDescriptionGenerator InitializeModelDescriptionGenerator(HttpConfiguration config) + { + ModelDescriptionGenerator modelGenerator = new ModelDescriptionGenerator(config); + Collection apis = config.Services.GetApiExplorer().ApiDescriptions; + foreach (ApiDescription api in apis) + { + ApiParameterDescription parameterDescription; + Type parameterType; + if (TryGetResourceParameter(api, config, out parameterDescription, out parameterType)) + { + modelGenerator.GetOrCreateModelDescription(parameterType); + } + } + return modelGenerator; + } + + private static void LogInvalidSampleAsError(HelpPageApiModel apiModel, object sample) + { + InvalidSample invalidSample = sample as InvalidSample; + if (invalidSample != null) + { + apiModel.ErrorMessages.Add(invalidSample.ErrorMessage); + } + } + } +} diff --git a/WebApp/Areas/HelpPage/ModelDescriptions/CollectionModelDescription.cs b/WebApp/Areas/HelpPage/ModelDescriptions/CollectionModelDescription.cs new file mode 100644 index 0000000..4ed4729 --- /dev/null +++ b/WebApp/Areas/HelpPage/ModelDescriptions/CollectionModelDescription.cs @@ -0,0 +1,7 @@ +namespace WebApp.Areas.HelpPage.ModelDescriptions +{ + public class CollectionModelDescription : ModelDescription + { + public ModelDescription ElementDescription { get; set; } + } +} \ No newline at end of file diff --git a/WebApp/Areas/HelpPage/ModelDescriptions/ComplexTypeModelDescription.cs b/WebApp/Areas/HelpPage/ModelDescriptions/ComplexTypeModelDescription.cs new file mode 100644 index 0000000..981b55c --- /dev/null +++ b/WebApp/Areas/HelpPage/ModelDescriptions/ComplexTypeModelDescription.cs @@ -0,0 +1,14 @@ +using System.Collections.ObjectModel; + +namespace WebApp.Areas.HelpPage.ModelDescriptions +{ + public class ComplexTypeModelDescription : ModelDescription + { + public ComplexTypeModelDescription() + { + Properties = new Collection(); + } + + public Collection Properties { get; private set; } + } +} \ No newline at end of file diff --git a/WebApp/Areas/HelpPage/ModelDescriptions/DictionaryModelDescription.cs b/WebApp/Areas/HelpPage/ModelDescriptions/DictionaryModelDescription.cs new file mode 100644 index 0000000..ef2cfc3 --- /dev/null +++ b/WebApp/Areas/HelpPage/ModelDescriptions/DictionaryModelDescription.cs @@ -0,0 +1,6 @@ +namespace WebApp.Areas.HelpPage.ModelDescriptions +{ + public class DictionaryModelDescription : KeyValuePairModelDescription + { + } +} \ No newline at end of file diff --git a/WebApp/Areas/HelpPage/ModelDescriptions/EnumTypeModelDescription.cs b/WebApp/Areas/HelpPage/ModelDescriptions/EnumTypeModelDescription.cs new file mode 100644 index 0000000..394e1ae --- /dev/null +++ b/WebApp/Areas/HelpPage/ModelDescriptions/EnumTypeModelDescription.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; +using System.Collections.ObjectModel; + +namespace WebApp.Areas.HelpPage.ModelDescriptions +{ + public class EnumTypeModelDescription : ModelDescription + { + public EnumTypeModelDescription() + { + Values = new Collection(); + } + + public Collection Values { get; private set; } + } +} \ No newline at end of file diff --git a/WebApp/Areas/HelpPage/ModelDescriptions/EnumValueDescription.cs b/WebApp/Areas/HelpPage/ModelDescriptions/EnumValueDescription.cs new file mode 100644 index 0000000..53617c1 --- /dev/null +++ b/WebApp/Areas/HelpPage/ModelDescriptions/EnumValueDescription.cs @@ -0,0 +1,11 @@ +namespace WebApp.Areas.HelpPage.ModelDescriptions +{ + public class EnumValueDescription + { + public string Documentation { get; set; } + + public string Name { get; set; } + + public string Value { get; set; } + } +} \ No newline at end of file diff --git a/WebApp/Areas/HelpPage/ModelDescriptions/IModelDocumentationProvider.cs b/WebApp/Areas/HelpPage/ModelDescriptions/IModelDocumentationProvider.cs new file mode 100644 index 0000000..4b55e61 --- /dev/null +++ b/WebApp/Areas/HelpPage/ModelDescriptions/IModelDocumentationProvider.cs @@ -0,0 +1,12 @@ +using System; +using System.Reflection; + +namespace WebApp.Areas.HelpPage.ModelDescriptions +{ + public interface IModelDocumentationProvider + { + string GetDocumentation(MemberInfo member); + + string GetDocumentation(Type type); + } +} \ No newline at end of file diff --git a/WebApp/Areas/HelpPage/ModelDescriptions/KeyValuePairModelDescription.cs b/WebApp/Areas/HelpPage/ModelDescriptions/KeyValuePairModelDescription.cs new file mode 100644 index 0000000..a85fb71 --- /dev/null +++ b/WebApp/Areas/HelpPage/ModelDescriptions/KeyValuePairModelDescription.cs @@ -0,0 +1,9 @@ +namespace WebApp.Areas.HelpPage.ModelDescriptions +{ + public class KeyValuePairModelDescription : ModelDescription + { + public ModelDescription KeyModelDescription { get; set; } + + public ModelDescription ValueModelDescription { get; set; } + } +} \ No newline at end of file diff --git a/WebApp/Areas/HelpPage/ModelDescriptions/ModelDescription.cs b/WebApp/Areas/HelpPage/ModelDescriptions/ModelDescription.cs new file mode 100644 index 0000000..fc2dafe --- /dev/null +++ b/WebApp/Areas/HelpPage/ModelDescriptions/ModelDescription.cs @@ -0,0 +1,16 @@ +using System; + +namespace WebApp.Areas.HelpPage.ModelDescriptions +{ + /// + /// Describes a type model. + /// + public abstract class ModelDescription + { + public string Documentation { get; set; } + + public Type ModelType { get; set; } + + public string Name { get; set; } + } +} \ No newline at end of file diff --git a/WebApp/Areas/HelpPage/ModelDescriptions/ModelDescriptionGenerator.cs b/WebApp/Areas/HelpPage/ModelDescriptions/ModelDescriptionGenerator.cs new file mode 100644 index 0000000..2dde690 --- /dev/null +++ b/WebApp/Areas/HelpPage/ModelDescriptions/ModelDescriptionGenerator.cs @@ -0,0 +1,451 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.ComponentModel.DataAnnotations; +using System.Globalization; +using System.Reflection; +using System.Runtime.Serialization; +using System.Web.Http; +using System.Web.Http.Description; +using System.Xml.Serialization; +using Newtonsoft.Json; + +namespace WebApp.Areas.HelpPage.ModelDescriptions +{ + /// + /// Generates model descriptions for given types. + /// + public class ModelDescriptionGenerator + { + // Modify this to support more data annotation attributes. + private readonly IDictionary> AnnotationTextGenerator = new Dictionary> + { + { typeof(RequiredAttribute), a => "Required" }, + { typeof(RangeAttribute), a => + { + RangeAttribute range = (RangeAttribute)a; + return String.Format(CultureInfo.CurrentCulture, "Range: inclusive between {0} and {1}", range.Minimum, range.Maximum); + } + }, + { typeof(MaxLengthAttribute), a => + { + MaxLengthAttribute maxLength = (MaxLengthAttribute)a; + return String.Format(CultureInfo.CurrentCulture, "Max length: {0}", maxLength.Length); + } + }, + { typeof(MinLengthAttribute), a => + { + MinLengthAttribute minLength = (MinLengthAttribute)a; + return String.Format(CultureInfo.CurrentCulture, "Min length: {0}", minLength.Length); + } + }, + { typeof(StringLengthAttribute), a => + { + StringLengthAttribute strLength = (StringLengthAttribute)a; + return String.Format(CultureInfo.CurrentCulture, "String length: inclusive between {0} and {1}", strLength.MinimumLength, strLength.MaximumLength); + } + }, + { typeof(DataTypeAttribute), a => + { + DataTypeAttribute dataType = (DataTypeAttribute)a; + return String.Format(CultureInfo.CurrentCulture, "Data type: {0}", dataType.CustomDataType ?? dataType.DataType.ToString()); + } + }, + { typeof(RegularExpressionAttribute), a => + { + RegularExpressionAttribute regularExpression = (RegularExpressionAttribute)a; + return String.Format(CultureInfo.CurrentCulture, "Matching regular expression pattern: {0}", regularExpression.Pattern); + } + }, + }; + + // Modify this to add more default documentations. + private readonly IDictionary DefaultTypeDocumentation = new Dictionary + { + { typeof(Int16), "integer" }, + { typeof(Int32), "integer" }, + { typeof(Int64), "integer" }, + { typeof(UInt16), "unsigned integer" }, + { typeof(UInt32), "unsigned integer" }, + { typeof(UInt64), "unsigned integer" }, + { typeof(Byte), "byte" }, + { typeof(Char), "character" }, + { typeof(SByte), "signed byte" }, + { typeof(Uri), "URI" }, + { typeof(Single), "decimal number" }, + { typeof(Double), "decimal number" }, + { typeof(Decimal), "decimal number" }, + { typeof(String), "string" }, + { typeof(Guid), "globally unique identifier" }, + { typeof(TimeSpan), "time interval" }, + { typeof(DateTime), "date" }, + { typeof(DateTimeOffset), "date" }, + { typeof(Boolean), "boolean" }, + }; + + private Lazy _documentationProvider; + + public ModelDescriptionGenerator(HttpConfiguration config) + { + if (config == null) + { + throw new ArgumentNullException("config"); + } + + _documentationProvider = new Lazy(() => config.Services.GetDocumentationProvider() as IModelDocumentationProvider); + GeneratedModels = new Dictionary(StringComparer.OrdinalIgnoreCase); + } + + public Dictionary GeneratedModels { get; private set; } + + private IModelDocumentationProvider DocumentationProvider + { + get + { + return _documentationProvider.Value; + } + } + + public ModelDescription GetOrCreateModelDescription(Type modelType) + { + if (modelType == null) + { + throw new ArgumentNullException("modelType"); + } + + Type underlyingType = Nullable.GetUnderlyingType(modelType); + if (underlyingType != null) + { + modelType = underlyingType; + } + + ModelDescription modelDescription; + string modelName = ModelNameHelper.GetModelName(modelType); + if (GeneratedModels.TryGetValue(modelName, out modelDescription)) + { + if (modelType != modelDescription.ModelType) + { + throw new InvalidOperationException( + String.Format( + CultureInfo.CurrentCulture, + "A model description could not be created. Duplicate model name '{0}' was found for types '{1}' and '{2}'. " + + "Use the [ModelName] attribute to change the model name for at least one of the types so that it has a unique name.", + modelName, + modelDescription.ModelType.FullName, + modelType.FullName)); + } + + return modelDescription; + } + + if (DefaultTypeDocumentation.ContainsKey(modelType)) + { + return GenerateSimpleTypeModelDescription(modelType); + } + + if (modelType.IsEnum) + { + return GenerateEnumTypeModelDescription(modelType); + } + + if (modelType.IsGenericType) + { + Type[] genericArguments = modelType.GetGenericArguments(); + + if (genericArguments.Length == 1) + { + Type enumerableType = typeof(IEnumerable<>).MakeGenericType(genericArguments); + if (enumerableType.IsAssignableFrom(modelType)) + { + return GenerateCollectionModelDescription(modelType, genericArguments[0]); + } + } + if (genericArguments.Length == 2) + { + Type dictionaryType = typeof(IDictionary<,>).MakeGenericType(genericArguments); + if (dictionaryType.IsAssignableFrom(modelType)) + { + return GenerateDictionaryModelDescription(modelType, genericArguments[0], genericArguments[1]); + } + + Type keyValuePairType = typeof(KeyValuePair<,>).MakeGenericType(genericArguments); + if (keyValuePairType.IsAssignableFrom(modelType)) + { + return GenerateKeyValuePairModelDescription(modelType, genericArguments[0], genericArguments[1]); + } + } + } + + if (modelType.IsArray) + { + Type elementType = modelType.GetElementType(); + return GenerateCollectionModelDescription(modelType, elementType); + } + + if (modelType == typeof(NameValueCollection)) + { + return GenerateDictionaryModelDescription(modelType, typeof(string), typeof(string)); + } + + if (typeof(IDictionary).IsAssignableFrom(modelType)) + { + return GenerateDictionaryModelDescription(modelType, typeof(object), typeof(object)); + } + + if (typeof(IEnumerable).IsAssignableFrom(modelType)) + { + return GenerateCollectionModelDescription(modelType, typeof(object)); + } + + return GenerateComplexTypeModelDescription(modelType); + } + + // Change this to provide different name for the member. + private static string GetMemberName(MemberInfo member, bool hasDataContractAttribute) + { + JsonPropertyAttribute jsonProperty = member.GetCustomAttribute(); + if (jsonProperty != null && !String.IsNullOrEmpty(jsonProperty.PropertyName)) + { + return jsonProperty.PropertyName; + } + + if (hasDataContractAttribute) + { + DataMemberAttribute dataMember = member.GetCustomAttribute(); + if (dataMember != null && !String.IsNullOrEmpty(dataMember.Name)) + { + return dataMember.Name; + } + } + + return member.Name; + } + + private static bool ShouldDisplayMember(MemberInfo member, bool hasDataContractAttribute) + { + JsonIgnoreAttribute jsonIgnore = member.GetCustomAttribute(); + XmlIgnoreAttribute xmlIgnore = member.GetCustomAttribute(); + IgnoreDataMemberAttribute ignoreDataMember = member.GetCustomAttribute(); + NonSerializedAttribute nonSerialized = member.GetCustomAttribute(); + ApiExplorerSettingsAttribute apiExplorerSetting = member.GetCustomAttribute(); + + bool hasMemberAttribute = member.DeclaringType.IsEnum ? + member.GetCustomAttribute() != null : + member.GetCustomAttribute() != null; + + // Display member only if all the followings are true: + // no JsonIgnoreAttribute + // no XmlIgnoreAttribute + // no IgnoreDataMemberAttribute + // no NonSerializedAttribute + // no ApiExplorerSettingsAttribute with IgnoreApi set to true + // no DataContractAttribute without DataMemberAttribute or EnumMemberAttribute + return jsonIgnore == null && + xmlIgnore == null && + ignoreDataMember == null && + nonSerialized == null && + (apiExplorerSetting == null || !apiExplorerSetting.IgnoreApi) && + (!hasDataContractAttribute || hasMemberAttribute); + } + + private string CreateDefaultDocumentation(Type type) + { + string documentation; + if (DefaultTypeDocumentation.TryGetValue(type, out documentation)) + { + return documentation; + } + if (DocumentationProvider != null) + { + documentation = DocumentationProvider.GetDocumentation(type); + } + + return documentation; + } + + private void GenerateAnnotations(MemberInfo property, ParameterDescription propertyModel) + { + List annotations = new List(); + + IEnumerable attributes = property.GetCustomAttributes(); + foreach (Attribute attribute in attributes) + { + Func textGenerator; + if (AnnotationTextGenerator.TryGetValue(attribute.GetType(), out textGenerator)) + { + annotations.Add( + new ParameterAnnotation + { + AnnotationAttribute = attribute, + Documentation = textGenerator(attribute) + }); + } + } + + // Rearrange the annotations + annotations.Sort((x, y) => + { + // Special-case RequiredAttribute so that it shows up on top + if (x.AnnotationAttribute is RequiredAttribute) + { + return -1; + } + if (y.AnnotationAttribute is RequiredAttribute) + { + return 1; + } + + // Sort the rest based on alphabetic order of the documentation + return String.Compare(x.Documentation, y.Documentation, StringComparison.OrdinalIgnoreCase); + }); + + foreach (ParameterAnnotation annotation in annotations) + { + propertyModel.Annotations.Add(annotation); + } + } + + private CollectionModelDescription GenerateCollectionModelDescription(Type modelType, Type elementType) + { + ModelDescription collectionModelDescription = GetOrCreateModelDescription(elementType); + if (collectionModelDescription != null) + { + return new CollectionModelDescription + { + Name = ModelNameHelper.GetModelName(modelType), + ModelType = modelType, + ElementDescription = collectionModelDescription + }; + } + + return null; + } + + private ModelDescription GenerateComplexTypeModelDescription(Type modelType) + { + ComplexTypeModelDescription complexModelDescription = new ComplexTypeModelDescription + { + Name = ModelNameHelper.GetModelName(modelType), + ModelType = modelType, + Documentation = CreateDefaultDocumentation(modelType) + }; + + GeneratedModels.Add(complexModelDescription.Name, complexModelDescription); + bool hasDataContractAttribute = modelType.GetCustomAttribute() != null; + PropertyInfo[] properties = modelType.GetProperties(BindingFlags.Public | BindingFlags.Instance); + foreach (PropertyInfo property in properties) + { + if (ShouldDisplayMember(property, hasDataContractAttribute)) + { + ParameterDescription propertyModel = new ParameterDescription + { + Name = GetMemberName(property, hasDataContractAttribute) + }; + + if (DocumentationProvider != null) + { + propertyModel.Documentation = DocumentationProvider.GetDocumentation(property); + } + + GenerateAnnotations(property, propertyModel); + complexModelDescription.Properties.Add(propertyModel); + propertyModel.TypeDescription = GetOrCreateModelDescription(property.PropertyType); + } + } + + FieldInfo[] fields = modelType.GetFields(BindingFlags.Public | BindingFlags.Instance); + foreach (FieldInfo field in fields) + { + if (ShouldDisplayMember(field, hasDataContractAttribute)) + { + ParameterDescription propertyModel = new ParameterDescription + { + Name = GetMemberName(field, hasDataContractAttribute) + }; + + if (DocumentationProvider != null) + { + propertyModel.Documentation = DocumentationProvider.GetDocumentation(field); + } + + complexModelDescription.Properties.Add(propertyModel); + propertyModel.TypeDescription = GetOrCreateModelDescription(field.FieldType); + } + } + + return complexModelDescription; + } + + private DictionaryModelDescription GenerateDictionaryModelDescription(Type modelType, Type keyType, Type valueType) + { + ModelDescription keyModelDescription = GetOrCreateModelDescription(keyType); + ModelDescription valueModelDescription = GetOrCreateModelDescription(valueType); + + return new DictionaryModelDescription + { + Name = ModelNameHelper.GetModelName(modelType), + ModelType = modelType, + KeyModelDescription = keyModelDescription, + ValueModelDescription = valueModelDescription + }; + } + + private EnumTypeModelDescription GenerateEnumTypeModelDescription(Type modelType) + { + EnumTypeModelDescription enumDescription = new EnumTypeModelDescription + { + Name = ModelNameHelper.GetModelName(modelType), + ModelType = modelType, + Documentation = CreateDefaultDocumentation(modelType) + }; + bool hasDataContractAttribute = modelType.GetCustomAttribute() != null; + foreach (FieldInfo field in modelType.GetFields(BindingFlags.Public | BindingFlags.Static)) + { + if (ShouldDisplayMember(field, hasDataContractAttribute)) + { + EnumValueDescription enumValue = new EnumValueDescription + { + Name = field.Name, + Value = field.GetRawConstantValue().ToString() + }; + if (DocumentationProvider != null) + { + enumValue.Documentation = DocumentationProvider.GetDocumentation(field); + } + enumDescription.Values.Add(enumValue); + } + } + GeneratedModels.Add(enumDescription.Name, enumDescription); + + return enumDescription; + } + + private KeyValuePairModelDescription GenerateKeyValuePairModelDescription(Type modelType, Type keyType, Type valueType) + { + ModelDescription keyModelDescription = GetOrCreateModelDescription(keyType); + ModelDescription valueModelDescription = GetOrCreateModelDescription(valueType); + + return new KeyValuePairModelDescription + { + Name = ModelNameHelper.GetModelName(modelType), + ModelType = modelType, + KeyModelDescription = keyModelDescription, + ValueModelDescription = valueModelDescription + }; + } + + private ModelDescription GenerateSimpleTypeModelDescription(Type modelType) + { + SimpleTypeModelDescription simpleModelDescription = new SimpleTypeModelDescription + { + Name = ModelNameHelper.GetModelName(modelType), + ModelType = modelType, + Documentation = CreateDefaultDocumentation(modelType) + }; + GeneratedModels.Add(simpleModelDescription.Name, simpleModelDescription); + + return simpleModelDescription; + } + } +} \ No newline at end of file diff --git a/WebApp/Areas/HelpPage/ModelDescriptions/ModelNameAttribute.cs b/WebApp/Areas/HelpPage/ModelDescriptions/ModelNameAttribute.cs new file mode 100644 index 0000000..8bb3a6f --- /dev/null +++ b/WebApp/Areas/HelpPage/ModelDescriptions/ModelNameAttribute.cs @@ -0,0 +1,18 @@ +using System; + +namespace WebApp.Areas.HelpPage.ModelDescriptions +{ + /// + /// Use this attribute to change the name of the generated for a type. + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum, AllowMultiple = false, Inherited = false)] + public sealed class ModelNameAttribute : Attribute + { + public ModelNameAttribute(string name) + { + Name = name; + } + + public string Name { get; private set; } + } +} \ No newline at end of file diff --git a/WebApp/Areas/HelpPage/ModelDescriptions/ModelNameHelper.cs b/WebApp/Areas/HelpPage/ModelDescriptions/ModelNameHelper.cs new file mode 100644 index 0000000..329932e --- /dev/null +++ b/WebApp/Areas/HelpPage/ModelDescriptions/ModelNameHelper.cs @@ -0,0 +1,36 @@ +using System; +using System.Globalization; +using System.Linq; +using System.Reflection; + +namespace WebApp.Areas.HelpPage.ModelDescriptions +{ + internal static class ModelNameHelper + { + // Modify this to provide custom model name mapping. + public static string GetModelName(Type type) + { + ModelNameAttribute modelNameAttribute = type.GetCustomAttribute(); + if (modelNameAttribute != null && !String.IsNullOrEmpty(modelNameAttribute.Name)) + { + return modelNameAttribute.Name; + } + + string modelName = type.Name; + if (type.IsGenericType) + { + // Format the generic type name to something like: GenericOfAgurment1AndArgument2 + Type genericType = type.GetGenericTypeDefinition(); + Type[] genericArguments = type.GetGenericArguments(); + string genericTypeName = genericType.Name; + + // Trim the generic parameter counts from the name + genericTypeName = genericTypeName.Substring(0, genericTypeName.IndexOf('`')); + string[] argumentTypeNames = genericArguments.Select(t => GetModelName(t)).ToArray(); + modelName = String.Format(CultureInfo.InvariantCulture, "{0}Of{1}", genericTypeName, String.Join("And", argumentTypeNames)); + } + + return modelName; + } + } +} \ No newline at end of file diff --git a/WebApp/Areas/HelpPage/ModelDescriptions/ParameterAnnotation.cs b/WebApp/Areas/HelpPage/ModelDescriptions/ParameterAnnotation.cs new file mode 100644 index 0000000..b83ed27 --- /dev/null +++ b/WebApp/Areas/HelpPage/ModelDescriptions/ParameterAnnotation.cs @@ -0,0 +1,11 @@ +using System; + +namespace WebApp.Areas.HelpPage.ModelDescriptions +{ + public class ParameterAnnotation + { + public Attribute AnnotationAttribute { get; set; } + + public string Documentation { get; set; } + } +} \ No newline at end of file diff --git a/WebApp/Areas/HelpPage/ModelDescriptions/ParameterDescription.cs b/WebApp/Areas/HelpPage/ModelDescriptions/ParameterDescription.cs new file mode 100644 index 0000000..d929d36 --- /dev/null +++ b/WebApp/Areas/HelpPage/ModelDescriptions/ParameterDescription.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; +using System.Collections.ObjectModel; + +namespace WebApp.Areas.HelpPage.ModelDescriptions +{ + public class ParameterDescription + { + public ParameterDescription() + { + Annotations = new Collection(); + } + + public Collection Annotations { get; private set; } + + public string Documentation { get; set; } + + public string Name { get; set; } + + public ModelDescription TypeDescription { get; set; } + } +} \ No newline at end of file diff --git a/WebApp/Areas/HelpPage/ModelDescriptions/SimpleTypeModelDescription.cs b/WebApp/Areas/HelpPage/ModelDescriptions/SimpleTypeModelDescription.cs new file mode 100644 index 0000000..747be2f --- /dev/null +++ b/WebApp/Areas/HelpPage/ModelDescriptions/SimpleTypeModelDescription.cs @@ -0,0 +1,6 @@ +namespace WebApp.Areas.HelpPage.ModelDescriptions +{ + public class SimpleTypeModelDescription : ModelDescription + { + } +} \ No newline at end of file diff --git a/WebApp/Areas/HelpPage/Models/HelpPageApiModel.cs b/WebApp/Areas/HelpPage/Models/HelpPageApiModel.cs new file mode 100644 index 0000000..113413b --- /dev/null +++ b/WebApp/Areas/HelpPage/Models/HelpPageApiModel.cs @@ -0,0 +1,108 @@ +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Net.Http.Headers; +using System.Web.Http.Description; +using WebApp.Areas.HelpPage.ModelDescriptions; + +namespace WebApp.Areas.HelpPage.Models +{ + /// + /// The model that represents an API displayed on the help page. + /// + public class HelpPageApiModel + { + /// + /// Initializes a new instance of the class. + /// + public HelpPageApiModel() + { + UriParameters = new Collection(); + SampleRequests = new Dictionary(); + SampleResponses = new Dictionary(); + ErrorMessages = new Collection(); + } + + /// + /// Gets or sets the that describes the API. + /// + public ApiDescription ApiDescription { get; set; } + + /// + /// Gets or sets the collection that describes the URI parameters for the API. + /// + public Collection UriParameters { get; private set; } + + /// + /// Gets or sets the documentation for the request. + /// + public string RequestDocumentation { get; set; } + + /// + /// Gets or sets the that describes the request body. + /// + public ModelDescription RequestModelDescription { get; set; } + + /// + /// Gets the request body parameter descriptions. + /// + public IList RequestBodyParameters + { + get + { + return GetParameterDescriptions(RequestModelDescription); + } + } + + /// + /// Gets or sets the that describes the resource. + /// + public ModelDescription ResourceDescription { get; set; } + + /// + /// Gets the resource property descriptions. + /// + public IList ResourceProperties + { + get + { + return GetParameterDescriptions(ResourceDescription); + } + } + + /// + /// Gets the sample requests associated with the API. + /// + public IDictionary SampleRequests { get; private set; } + + /// + /// Gets the sample responses associated with the API. + /// + public IDictionary SampleResponses { get; private set; } + + /// + /// Gets the error messages associated with this model. + /// + public Collection ErrorMessages { get; private set; } + + private static IList GetParameterDescriptions(ModelDescription modelDescription) + { + ComplexTypeModelDescription complexTypeModelDescription = modelDescription as ComplexTypeModelDescription; + if (complexTypeModelDescription != null) + { + return complexTypeModelDescription.Properties; + } + + CollectionModelDescription collectionModelDescription = modelDescription as CollectionModelDescription; + if (collectionModelDescription != null) + { + complexTypeModelDescription = collectionModelDescription.ElementDescription as ComplexTypeModelDescription; + if (complexTypeModelDescription != null) + { + return complexTypeModelDescription.Properties; + } + } + + return null; + } + } +} \ No newline at end of file diff --git a/WebApp/Areas/HelpPage/SampleGeneration/HelpPageSampleGenerator.cs b/WebApp/Areas/HelpPage/SampleGeneration/HelpPageSampleGenerator.cs new file mode 100644 index 0000000..cd4fd8c --- /dev/null +++ b/WebApp/Areas/HelpPage/SampleGeneration/HelpPageSampleGenerator.cs @@ -0,0 +1,444 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Net.Http.Formatting; +using System.Net.Http.Headers; +using System.Web.Http.Description; +using System.Xml.Linq; +using Newtonsoft.Json; + +namespace WebApp.Areas.HelpPage +{ + /// + /// This class will generate the samples for the help page. + /// + public class HelpPageSampleGenerator + { + /// + /// Initializes a new instance of the class. + /// + public HelpPageSampleGenerator() + { + ActualHttpMessageTypes = new Dictionary(); + ActionSamples = new Dictionary(); + SampleObjects = new Dictionary(); + SampleObjectFactories = new List> + { + DefaultSampleObjectFactory, + }; + } + + /// + /// Gets CLR types that are used as the content of or . + /// + public IDictionary ActualHttpMessageTypes { get; internal set; } + + /// + /// Gets the objects that are used directly as samples for certain actions. + /// + public IDictionary ActionSamples { get; internal set; } + + /// + /// Gets the objects that are serialized as samples by the supported formatters. + /// + public IDictionary SampleObjects { get; internal set; } + + /// + /// Gets factories for the objects that the supported formatters will serialize as samples. Processed in order, + /// stopping when the factory successfully returns a non- object. + /// + /// + /// Collection includes just initially. Use + /// SampleObjectFactories.Insert(0, func) to provide an override and + /// SampleObjectFactories.Add(func) to provide a fallback. + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", + Justification = "This is an appropriate nesting of generic types")] + public IList> SampleObjectFactories { get; private set; } + + /// + /// Gets the request body samples for a given . + /// + /// The . + /// The samples keyed by media type. + public IDictionary GetSampleRequests(ApiDescription api) + { + return GetSample(api, SampleDirection.Request); + } + + /// + /// Gets the response body samples for a given . + /// + /// The . + /// The samples keyed by media type. + public IDictionary GetSampleResponses(ApiDescription api) + { + return GetSample(api, SampleDirection.Response); + } + + /// + /// Gets the request or response body samples. + /// + /// The . + /// The value indicating whether the sample is for a request or for a response. + /// The samples keyed by media type. + public virtual IDictionary GetSample(ApiDescription api, SampleDirection sampleDirection) + { + if (api == null) + { + throw new ArgumentNullException("api"); + } + string controllerName = api.ActionDescriptor.ControllerDescriptor.ControllerName; + string actionName = api.ActionDescriptor.ActionName; + IEnumerable parameterNames = api.ParameterDescriptions.Select(p => p.Name); + Collection formatters; + Type type = ResolveType(api, controllerName, actionName, parameterNames, sampleDirection, out formatters); + var samples = new Dictionary(); + + // Use the samples provided directly for actions + var actionSamples = GetAllActionSamples(controllerName, actionName, parameterNames, sampleDirection); + foreach (var actionSample in actionSamples) + { + samples.Add(actionSample.Key.MediaType, WrapSampleIfString(actionSample.Value)); + } + + // Do the sample generation based on formatters only if an action doesn't return an HttpResponseMessage. + // Here we cannot rely on formatters because we don't know what's in the HttpResponseMessage, it might not even use formatters. + if (type != null && !typeof(HttpResponseMessage).IsAssignableFrom(type)) + { + object sampleObject = GetSampleObject(type); + foreach (var formatter in formatters) + { + foreach (MediaTypeHeaderValue mediaType in formatter.SupportedMediaTypes) + { + if (!samples.ContainsKey(mediaType)) + { + object sample = GetActionSample(controllerName, actionName, parameterNames, type, formatter, mediaType, sampleDirection); + + // If no sample found, try generate sample using formatter and sample object + if (sample == null && sampleObject != null) + { + sample = WriteSampleObjectUsingFormatter(formatter, sampleObject, type, mediaType); + } + + samples.Add(mediaType, WrapSampleIfString(sample)); + } + } + } + } + + return samples; + } + + /// + /// Search for samples that are provided directly through . + /// + /// Name of the controller. + /// Name of the action. + /// The parameter names. + /// The CLR type. + /// The formatter. + /// The media type. + /// The value indicating whether the sample is for a request or for a response. + /// The sample that matches the parameters. + public virtual object GetActionSample(string controllerName, string actionName, IEnumerable parameterNames, Type type, MediaTypeFormatter formatter, MediaTypeHeaderValue mediaType, SampleDirection sampleDirection) + { + object sample; + + // First, try to get the sample provided for the specified mediaType, sampleDirection, controllerName, actionName and parameterNames. + // If not found, try to get the sample provided for the specified mediaType, sampleDirection, controllerName and actionName regardless of the parameterNames. + // If still not found, try to get the sample provided for the specified mediaType and type. + // Finally, try to get the sample provided for the specified mediaType. + if (ActionSamples.TryGetValue(new HelpPageSampleKey(mediaType, sampleDirection, controllerName, actionName, parameterNames), out sample) || + ActionSamples.TryGetValue(new HelpPageSampleKey(mediaType, sampleDirection, controllerName, actionName, new[] { "*" }), out sample) || + ActionSamples.TryGetValue(new HelpPageSampleKey(mediaType, type), out sample) || + ActionSamples.TryGetValue(new HelpPageSampleKey(mediaType), out sample)) + { + return sample; + } + + return null; + } + + /// + /// Gets the sample object that will be serialized by the formatters. + /// First, it will look at the . If no sample object is found, it will try to create + /// one using (which wraps an ) and other + /// factories in . + /// + /// The type. + /// The sample object. + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", + Justification = "Even if all items in SampleObjectFactories throw, problem will be visible as missing sample.")] + public virtual object GetSampleObject(Type type) + { + object sampleObject; + + if (!SampleObjects.TryGetValue(type, out sampleObject)) + { + // No specific object available, try our factories. + foreach (Func factory in SampleObjectFactories) + { + if (factory == null) + { + continue; + } + + try + { + sampleObject = factory(this, type); + if (sampleObject != null) + { + break; + } + } + catch + { + // Ignore any problems encountered in the factory; go on to the next one (if any). + } + } + } + + return sampleObject; + } + + /// + /// Resolves the actual type of passed to the in an action. + /// + /// The . + /// The type. + public virtual Type ResolveHttpRequestMessageType(ApiDescription api) + { + string controllerName = api.ActionDescriptor.ControllerDescriptor.ControllerName; + string actionName = api.ActionDescriptor.ActionName; + IEnumerable parameterNames = api.ParameterDescriptions.Select(p => p.Name); + Collection formatters; + return ResolveType(api, controllerName, actionName, parameterNames, SampleDirection.Request, out formatters); + } + + /// + /// Resolves the type of the action parameter or return value when or is used. + /// + /// The . + /// Name of the controller. + /// Name of the action. + /// The parameter names. + /// The value indicating whether the sample is for a request or a response. + /// The formatters. + [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", Justification = "This is only used in advanced scenarios.")] + public virtual Type ResolveType(ApiDescription api, string controllerName, string actionName, IEnumerable parameterNames, SampleDirection sampleDirection, out Collection formatters) + { + if (!Enum.IsDefined(typeof(SampleDirection), sampleDirection)) + { + throw new InvalidEnumArgumentException("sampleDirection", (int)sampleDirection, typeof(SampleDirection)); + } + if (api == null) + { + throw new ArgumentNullException("api"); + } + Type type; + if (ActualHttpMessageTypes.TryGetValue(new HelpPageSampleKey(sampleDirection, controllerName, actionName, parameterNames), out type) || + ActualHttpMessageTypes.TryGetValue(new HelpPageSampleKey(sampleDirection, controllerName, actionName, new[] { "*" }), out type)) + { + // Re-compute the supported formatters based on type + Collection newFormatters = new Collection(); + foreach (var formatter in api.ActionDescriptor.Configuration.Formatters) + { + if (IsFormatSupported(sampleDirection, formatter, type)) + { + newFormatters.Add(formatter); + } + } + formatters = newFormatters; + } + else + { + switch (sampleDirection) + { + case SampleDirection.Request: + ApiParameterDescription requestBodyParameter = api.ParameterDescriptions.FirstOrDefault(p => p.Source == ApiParameterSource.FromBody); + type = requestBodyParameter == null ? null : requestBodyParameter.ParameterDescriptor.ParameterType; + formatters = api.SupportedRequestBodyFormatters; + break; + case SampleDirection.Response: + default: + type = api.ResponseDescription.ResponseType ?? api.ResponseDescription.DeclaredType; + formatters = api.SupportedResponseFormatters; + break; + } + } + + return type; + } + + /// + /// Writes the sample object using formatter. + /// + /// The formatter. + /// The value. + /// The type. + /// Type of the media. + /// + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "The exception is recorded as InvalidSample.")] + public virtual object WriteSampleObjectUsingFormatter(MediaTypeFormatter formatter, object value, Type type, MediaTypeHeaderValue mediaType) + { + if (formatter == null) + { + throw new ArgumentNullException("formatter"); + } + if (mediaType == null) + { + throw new ArgumentNullException("mediaType"); + } + + object sample = String.Empty; + MemoryStream ms = null; + HttpContent content = null; + try + { + if (formatter.CanWriteType(type)) + { + ms = new MemoryStream(); + content = new ObjectContent(type, value, formatter, mediaType); + formatter.WriteToStreamAsync(type, value, ms, content, null).Wait(); + ms.Position = 0; + StreamReader reader = new StreamReader(ms); + string serializedSampleString = reader.ReadToEnd(); + if (mediaType.MediaType.ToUpperInvariant().Contains("XML")) + { + serializedSampleString = TryFormatXml(serializedSampleString); + } + else if (mediaType.MediaType.ToUpperInvariant().Contains("JSON")) + { + serializedSampleString = TryFormatJson(serializedSampleString); + } + + sample = new TextSample(serializedSampleString); + } + else + { + sample = new InvalidSample(String.Format( + CultureInfo.CurrentCulture, + "Failed to generate the sample for media type '{0}'. Cannot use formatter '{1}' to write type '{2}'.", + mediaType, + formatter.GetType().Name, + type.Name)); + } + } + catch (Exception e) + { + sample = new InvalidSample(String.Format( + CultureInfo.CurrentCulture, + "An exception has occurred while using the formatter '{0}' to generate sample for media type '{1}'. Exception message: {2}", + formatter.GetType().Name, + mediaType.MediaType, + UnwrapException(e).Message)); + } + finally + { + if (ms != null) + { + ms.Dispose(); + } + if (content != null) + { + content.Dispose(); + } + } + + return sample; + } + + internal static Exception UnwrapException(Exception exception) + { + AggregateException aggregateException = exception as AggregateException; + if (aggregateException != null) + { + return aggregateException.Flatten().InnerException; + } + return exception; + } + + // Default factory for sample objects + private static object DefaultSampleObjectFactory(HelpPageSampleGenerator sampleGenerator, Type type) + { + // Try to create a default sample object + ObjectGenerator objectGenerator = new ObjectGenerator(); + return objectGenerator.GenerateObject(type); + } + + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Handling the failure by returning the original string.")] + private static string TryFormatJson(string str) + { + try + { + object parsedJson = JsonConvert.DeserializeObject(str); + return JsonConvert.SerializeObject(parsedJson, Formatting.Indented); + } + catch + { + // can't parse JSON, return the original string + return str; + } + } + + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Handling the failure by returning the original string.")] + private static string TryFormatXml(string str) + { + try + { + XDocument xml = XDocument.Parse(str); + return xml.ToString(); + } + catch + { + // can't parse XML, return the original string + return str; + } + } + + private static bool IsFormatSupported(SampleDirection sampleDirection, MediaTypeFormatter formatter, Type type) + { + switch (sampleDirection) + { + case SampleDirection.Request: + return formatter.CanReadType(type); + case SampleDirection.Response: + return formatter.CanWriteType(type); + } + return false; + } + + private IEnumerable> GetAllActionSamples(string controllerName, string actionName, IEnumerable parameterNames, SampleDirection sampleDirection) + { + HashSet parameterNamesSet = new HashSet(parameterNames, StringComparer.OrdinalIgnoreCase); + foreach (var sample in ActionSamples) + { + HelpPageSampleKey sampleKey = sample.Key; + if (String.Equals(controllerName, sampleKey.ControllerName, StringComparison.OrdinalIgnoreCase) && + String.Equals(actionName, sampleKey.ActionName, StringComparison.OrdinalIgnoreCase) && + (sampleKey.ParameterNames.SetEquals(new[] { "*" }) || parameterNamesSet.SetEquals(sampleKey.ParameterNames)) && + sampleDirection == sampleKey.SampleDirection) + { + yield return sample; + } + } + } + + private static object WrapSampleIfString(object sample) + { + string stringSample = sample as string; + if (stringSample != null) + { + return new TextSample(stringSample); + } + + return sample; + } + } +} \ No newline at end of file diff --git a/WebApp/Areas/HelpPage/SampleGeneration/HelpPageSampleKey.cs b/WebApp/Areas/HelpPage/SampleGeneration/HelpPageSampleKey.cs new file mode 100644 index 0000000..f8c2840 --- /dev/null +++ b/WebApp/Areas/HelpPage/SampleGeneration/HelpPageSampleKey.cs @@ -0,0 +1,172 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Net.Http.Headers; + +namespace WebApp.Areas.HelpPage +{ + /// + /// This is used to identify the place where the sample should be applied. + /// + public class HelpPageSampleKey + { + /// + /// Creates a new based on media type. + /// + /// The media type. + public HelpPageSampleKey(MediaTypeHeaderValue mediaType) + { + if (mediaType == null) + { + throw new ArgumentNullException("mediaType"); + } + + ActionName = String.Empty; + ControllerName = String.Empty; + MediaType = mediaType; + ParameterNames = new HashSet(StringComparer.OrdinalIgnoreCase); + } + + /// + /// Creates a new based on media type and CLR type. + /// + /// The media type. + /// The CLR type. + public HelpPageSampleKey(MediaTypeHeaderValue mediaType, Type type) + : this(mediaType) + { + if (type == null) + { + throw new ArgumentNullException("type"); + } + + ParameterType = type; + } + + /// + /// Creates a new based on , controller name, action name and parameter names. + /// + /// The . + /// Name of the controller. + /// Name of the action. + /// The parameter names. + public HelpPageSampleKey(SampleDirection sampleDirection, string controllerName, string actionName, IEnumerable parameterNames) + { + if (!Enum.IsDefined(typeof(SampleDirection), sampleDirection)) + { + throw new InvalidEnumArgumentException("sampleDirection", (int)sampleDirection, typeof(SampleDirection)); + } + if (controllerName == null) + { + throw new ArgumentNullException("controllerName"); + } + if (actionName == null) + { + throw new ArgumentNullException("actionName"); + } + if (parameterNames == null) + { + throw new ArgumentNullException("parameterNames"); + } + + ControllerName = controllerName; + ActionName = actionName; + ParameterNames = new HashSet(parameterNames, StringComparer.OrdinalIgnoreCase); + SampleDirection = sampleDirection; + } + + /// + /// Creates a new based on media type, , controller name, action name and parameter names. + /// + /// The media type. + /// The . + /// Name of the controller. + /// Name of the action. + /// The parameter names. + public HelpPageSampleKey(MediaTypeHeaderValue mediaType, SampleDirection sampleDirection, string controllerName, string actionName, IEnumerable parameterNames) + : this(sampleDirection, controllerName, actionName, parameterNames) + { + if (mediaType == null) + { + throw new ArgumentNullException("mediaType"); + } + + MediaType = mediaType; + } + + /// + /// Gets the name of the controller. + /// + /// + /// The name of the controller. + /// + public string ControllerName { get; private set; } + + /// + /// Gets the name of the action. + /// + /// + /// The name of the action. + /// + public string ActionName { get; private set; } + + /// + /// Gets the media type. + /// + /// + /// The media type. + /// + public MediaTypeHeaderValue MediaType { get; private set; } + + /// + /// Gets the parameter names. + /// + public HashSet ParameterNames { get; private set; } + + public Type ParameterType { get; private set; } + + /// + /// Gets the . + /// + public SampleDirection? SampleDirection { get; private set; } + + public override bool Equals(object obj) + { + HelpPageSampleKey otherKey = obj as HelpPageSampleKey; + if (otherKey == null) + { + return false; + } + + return String.Equals(ControllerName, otherKey.ControllerName, StringComparison.OrdinalIgnoreCase) && + String.Equals(ActionName, otherKey.ActionName, StringComparison.OrdinalIgnoreCase) && + (MediaType == otherKey.MediaType || (MediaType != null && MediaType.Equals(otherKey.MediaType))) && + ParameterType == otherKey.ParameterType && + SampleDirection == otherKey.SampleDirection && + ParameterNames.SetEquals(otherKey.ParameterNames); + } + + public override int GetHashCode() + { + int hashCode = ControllerName.ToUpperInvariant().GetHashCode() ^ ActionName.ToUpperInvariant().GetHashCode(); + if (MediaType != null) + { + hashCode ^= MediaType.GetHashCode(); + } + if (SampleDirection != null) + { + hashCode ^= SampleDirection.GetHashCode(); + } + if (ParameterType != null) + { + hashCode ^= ParameterType.GetHashCode(); + } + foreach (string parameterName in ParameterNames) + { + hashCode ^= parameterName.ToUpperInvariant().GetHashCode(); + } + + return hashCode; + } + } +} diff --git a/WebApp/Areas/HelpPage/SampleGeneration/ImageSample.cs b/WebApp/Areas/HelpPage/SampleGeneration/ImageSample.cs new file mode 100644 index 0000000..2106811 --- /dev/null +++ b/WebApp/Areas/HelpPage/SampleGeneration/ImageSample.cs @@ -0,0 +1,41 @@ +using System; + +namespace WebApp.Areas.HelpPage +{ + /// + /// This represents an image sample on the help page. There's a display template named ImageSample associated with this class. + /// + public class ImageSample + { + /// + /// Initializes a new instance of the class. + /// + /// The URL of an image. + public ImageSample(string src) + { + if (src == null) + { + throw new ArgumentNullException("src"); + } + Src = src; + } + + public string Src { get; private set; } + + public override bool Equals(object obj) + { + ImageSample other = obj as ImageSample; + return other != null && Src == other.Src; + } + + public override int GetHashCode() + { + return Src.GetHashCode(); + } + + public override string ToString() + { + return Src; + } + } +} \ No newline at end of file diff --git a/WebApp/Areas/HelpPage/SampleGeneration/InvalidSample.cs b/WebApp/Areas/HelpPage/SampleGeneration/InvalidSample.cs new file mode 100644 index 0000000..c5d5ff8 --- /dev/null +++ b/WebApp/Areas/HelpPage/SampleGeneration/InvalidSample.cs @@ -0,0 +1,37 @@ +using System; + +namespace WebApp.Areas.HelpPage +{ + /// + /// This represents an invalid sample on the help page. There's a display template named InvalidSample associated with this class. + /// + public class InvalidSample + { + public InvalidSample(string errorMessage) + { + if (errorMessage == null) + { + throw new ArgumentNullException("errorMessage"); + } + ErrorMessage = errorMessage; + } + + public string ErrorMessage { get; private set; } + + public override bool Equals(object obj) + { + InvalidSample other = obj as InvalidSample; + return other != null && ErrorMessage == other.ErrorMessage; + } + + public override int GetHashCode() + { + return ErrorMessage.GetHashCode(); + } + + public override string ToString() + { + return ErrorMessage; + } + } +} \ No newline at end of file diff --git a/WebApp/Areas/HelpPage/SampleGeneration/ObjectGenerator.cs b/WebApp/Areas/HelpPage/SampleGeneration/ObjectGenerator.cs new file mode 100644 index 0000000..e2bfb5c --- /dev/null +++ b/WebApp/Areas/HelpPage/SampleGeneration/ObjectGenerator.cs @@ -0,0 +1,456 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Linq; +using System.Reflection; + +namespace WebApp.Areas.HelpPage +{ + /// + /// This class will create an object of a given type and populate it with sample data. + /// + public class ObjectGenerator + { + internal const int DefaultCollectionSize = 2; + private readonly SimpleTypeObjectGenerator SimpleObjectGenerator = new SimpleTypeObjectGenerator(); + + /// + /// Generates an object for a given type. The type needs to be public, have a public default constructor and settable public properties/fields. Currently it supports the following types: + /// Simple types: , , , , , etc. + /// Complex types: POCO types. + /// Nullables: . + /// Arrays: arrays of simple types or complex types. + /// Key value pairs: + /// Tuples: , , etc + /// Dictionaries: or anything deriving from . + /// Collections: , , , , , or anything deriving from or . + /// Queryables: , . + /// + /// The type. + /// An object of the given type. + public object GenerateObject(Type type) + { + return GenerateObject(type, new Dictionary()); + } + + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Here we just want to return null if anything goes wrong.")] + private object GenerateObject(Type type, Dictionary createdObjectReferences) + { + try + { + if (SimpleTypeObjectGenerator.CanGenerateObject(type)) + { + return SimpleObjectGenerator.GenerateObject(type); + } + + if (type.IsArray) + { + return GenerateArray(type, DefaultCollectionSize, createdObjectReferences); + } + + if (type.IsGenericType) + { + return GenerateGenericType(type, DefaultCollectionSize, createdObjectReferences); + } + + if (type == typeof(IDictionary)) + { + return GenerateDictionary(typeof(Hashtable), DefaultCollectionSize, createdObjectReferences); + } + + if (typeof(IDictionary).IsAssignableFrom(type)) + { + return GenerateDictionary(type, DefaultCollectionSize, createdObjectReferences); + } + + if (type == typeof(IList) || + type == typeof(IEnumerable) || + type == typeof(ICollection)) + { + return GenerateCollection(typeof(ArrayList), DefaultCollectionSize, createdObjectReferences); + } + + if (typeof(IList).IsAssignableFrom(type)) + { + return GenerateCollection(type, DefaultCollectionSize, createdObjectReferences); + } + + if (type == typeof(IQueryable)) + { + return GenerateQueryable(type, DefaultCollectionSize, createdObjectReferences); + } + + if (type.IsEnum) + { + return GenerateEnum(type); + } + + if (type.IsPublic || type.IsNestedPublic) + { + return GenerateComplexObject(type, createdObjectReferences); + } + } + catch + { + // Returns null if anything fails + return null; + } + + return null; + } + + private static object GenerateGenericType(Type type, int collectionSize, Dictionary createdObjectReferences) + { + Type genericTypeDefinition = type.GetGenericTypeDefinition(); + if (genericTypeDefinition == typeof(Nullable<>)) + { + return GenerateNullable(type, createdObjectReferences); + } + + if (genericTypeDefinition == typeof(KeyValuePair<,>)) + { + return GenerateKeyValuePair(type, createdObjectReferences); + } + + if (IsTuple(genericTypeDefinition)) + { + return GenerateTuple(type, createdObjectReferences); + } + + Type[] genericArguments = type.GetGenericArguments(); + if (genericArguments.Length == 1) + { + if (genericTypeDefinition == typeof(IList<>) || + genericTypeDefinition == typeof(IEnumerable<>) || + genericTypeDefinition == typeof(ICollection<>)) + { + Type collectionType = typeof(List<>).MakeGenericType(genericArguments); + return GenerateCollection(collectionType, collectionSize, createdObjectReferences); + } + + if (genericTypeDefinition == typeof(IQueryable<>)) + { + return GenerateQueryable(type, collectionSize, createdObjectReferences); + } + + Type closedCollectionType = typeof(ICollection<>).MakeGenericType(genericArguments[0]); + if (closedCollectionType.IsAssignableFrom(type)) + { + return GenerateCollection(type, collectionSize, createdObjectReferences); + } + } + + if (genericArguments.Length == 2) + { + if (genericTypeDefinition == typeof(IDictionary<,>)) + { + Type dictionaryType = typeof(Dictionary<,>).MakeGenericType(genericArguments); + return GenerateDictionary(dictionaryType, collectionSize, createdObjectReferences); + } + + Type closedDictionaryType = typeof(IDictionary<,>).MakeGenericType(genericArguments[0], genericArguments[1]); + if (closedDictionaryType.IsAssignableFrom(type)) + { + return GenerateDictionary(type, collectionSize, createdObjectReferences); + } + } + + if (type.IsPublic || type.IsNestedPublic) + { + return GenerateComplexObject(type, createdObjectReferences); + } + + return null; + } + + private static object GenerateTuple(Type type, Dictionary createdObjectReferences) + { + Type[] genericArgs = type.GetGenericArguments(); + object[] parameterValues = new object[genericArgs.Length]; + bool failedToCreateTuple = true; + ObjectGenerator objectGenerator = new ObjectGenerator(); + for (int i = 0; i < genericArgs.Length; i++) + { + parameterValues[i] = objectGenerator.GenerateObject(genericArgs[i], createdObjectReferences); + failedToCreateTuple &= parameterValues[i] == null; + } + if (failedToCreateTuple) + { + return null; + } + object result = Activator.CreateInstance(type, parameterValues); + return result; + } + + private static bool IsTuple(Type genericTypeDefinition) + { + return genericTypeDefinition == typeof(Tuple<>) || + genericTypeDefinition == typeof(Tuple<,>) || + genericTypeDefinition == typeof(Tuple<,,>) || + genericTypeDefinition == typeof(Tuple<,,,>) || + genericTypeDefinition == typeof(Tuple<,,,,>) || + genericTypeDefinition == typeof(Tuple<,,,,,>) || + genericTypeDefinition == typeof(Tuple<,,,,,,>) || + genericTypeDefinition == typeof(Tuple<,,,,,,,>); + } + + private static object GenerateKeyValuePair(Type keyValuePairType, Dictionary createdObjectReferences) + { + Type[] genericArgs = keyValuePairType.GetGenericArguments(); + Type typeK = genericArgs[0]; + Type typeV = genericArgs[1]; + ObjectGenerator objectGenerator = new ObjectGenerator(); + object keyObject = objectGenerator.GenerateObject(typeK, createdObjectReferences); + object valueObject = objectGenerator.GenerateObject(typeV, createdObjectReferences); + if (keyObject == null && valueObject == null) + { + // Failed to create key and values + return null; + } + object result = Activator.CreateInstance(keyValuePairType, keyObject, valueObject); + return result; + } + + private static object GenerateArray(Type arrayType, int size, Dictionary createdObjectReferences) + { + Type type = arrayType.GetElementType(); + Array result = Array.CreateInstance(type, size); + bool areAllElementsNull = true; + ObjectGenerator objectGenerator = new ObjectGenerator(); + for (int i = 0; i < size; i++) + { + object element = objectGenerator.GenerateObject(type, createdObjectReferences); + result.SetValue(element, i); + areAllElementsNull &= element == null; + } + + if (areAllElementsNull) + { + return null; + } + + return result; + } + + private static object GenerateDictionary(Type dictionaryType, int size, Dictionary createdObjectReferences) + { + Type typeK = typeof(object); + Type typeV = typeof(object); + if (dictionaryType.IsGenericType) + { + Type[] genericArgs = dictionaryType.GetGenericArguments(); + typeK = genericArgs[0]; + typeV = genericArgs[1]; + } + + object result = Activator.CreateInstance(dictionaryType); + MethodInfo addMethod = dictionaryType.GetMethod("Add") ?? dictionaryType.GetMethod("TryAdd"); + MethodInfo containsMethod = dictionaryType.GetMethod("Contains") ?? dictionaryType.GetMethod("ContainsKey"); + ObjectGenerator objectGenerator = new ObjectGenerator(); + for (int i = 0; i < size; i++) + { + object newKey = objectGenerator.GenerateObject(typeK, createdObjectReferences); + if (newKey == null) + { + // Cannot generate a valid key + return null; + } + + bool containsKey = (bool)containsMethod.Invoke(result, new object[] { newKey }); + if (!containsKey) + { + object newValue = objectGenerator.GenerateObject(typeV, createdObjectReferences); + addMethod.Invoke(result, new object[] { newKey, newValue }); + } + } + + return result; + } + + private static object GenerateEnum(Type enumType) + { + Array possibleValues = Enum.GetValues(enumType); + if (possibleValues.Length > 0) + { + return possibleValues.GetValue(0); + } + return null; + } + + private static object GenerateQueryable(Type queryableType, int size, Dictionary createdObjectReferences) + { + bool isGeneric = queryableType.IsGenericType; + object list; + if (isGeneric) + { + Type listType = typeof(List<>).MakeGenericType(queryableType.GetGenericArguments()); + list = GenerateCollection(listType, size, createdObjectReferences); + } + else + { + list = GenerateArray(typeof(object[]), size, createdObjectReferences); + } + if (list == null) + { + return null; + } + if (isGeneric) + { + Type argumentType = typeof(IEnumerable<>).MakeGenericType(queryableType.GetGenericArguments()); + MethodInfo asQueryableMethod = typeof(Queryable).GetMethod("AsQueryable", new[] { argumentType }); + return asQueryableMethod.Invoke(null, new[] { list }); + } + + return Queryable.AsQueryable((IEnumerable)list); + } + + private static object GenerateCollection(Type collectionType, int size, Dictionary createdObjectReferences) + { + Type type = collectionType.IsGenericType ? + collectionType.GetGenericArguments()[0] : + typeof(object); + object result = Activator.CreateInstance(collectionType); + MethodInfo addMethod = collectionType.GetMethod("Add"); + bool areAllElementsNull = true; + ObjectGenerator objectGenerator = new ObjectGenerator(); + for (int i = 0; i < size; i++) + { + object element = objectGenerator.GenerateObject(type, createdObjectReferences); + addMethod.Invoke(result, new object[] { element }); + areAllElementsNull &= element == null; + } + + if (areAllElementsNull) + { + return null; + } + + return result; + } + + private static object GenerateNullable(Type nullableType, Dictionary createdObjectReferences) + { + Type type = nullableType.GetGenericArguments()[0]; + ObjectGenerator objectGenerator = new ObjectGenerator(); + return objectGenerator.GenerateObject(type, createdObjectReferences); + } + + private static object GenerateComplexObject(Type type, Dictionary createdObjectReferences) + { + object result = null; + + if (createdObjectReferences.TryGetValue(type, out result)) + { + // The object has been created already, just return it. This will handle the circular reference case. + return result; + } + + if (type.IsValueType) + { + result = Activator.CreateInstance(type); + } + else + { + ConstructorInfo defaultCtor = type.GetConstructor(Type.EmptyTypes); + if (defaultCtor == null) + { + // Cannot instantiate the type because it doesn't have a default constructor + return null; + } + + result = defaultCtor.Invoke(new object[0]); + } + createdObjectReferences.Add(type, result); + SetPublicProperties(type, result, createdObjectReferences); + SetPublicFields(type, result, createdObjectReferences); + return result; + } + + private static void SetPublicProperties(Type type, object obj, Dictionary createdObjectReferences) + { + PropertyInfo[] properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance); + ObjectGenerator objectGenerator = new ObjectGenerator(); + foreach (PropertyInfo property in properties) + { + if (property.CanWrite) + { + object propertyValue = objectGenerator.GenerateObject(property.PropertyType, createdObjectReferences); + property.SetValue(obj, propertyValue, null); + } + } + } + + private static void SetPublicFields(Type type, object obj, Dictionary createdObjectReferences) + { + FieldInfo[] fields = type.GetFields(BindingFlags.Public | BindingFlags.Instance); + ObjectGenerator objectGenerator = new ObjectGenerator(); + foreach (FieldInfo field in fields) + { + object fieldValue = objectGenerator.GenerateObject(field.FieldType, createdObjectReferences); + field.SetValue(obj, fieldValue); + } + } + + private class SimpleTypeObjectGenerator + { + private long _index = 0; + private static readonly Dictionary> DefaultGenerators = InitializeGenerators(); + + [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification = "These are simple type factories and cannot be split up.")] + private static Dictionary> InitializeGenerators() + { + return new Dictionary> + { + { typeof(Boolean), index => true }, + { typeof(Byte), index => (Byte)64 }, + { typeof(Char), index => (Char)65 }, + { typeof(DateTime), index => DateTime.Now }, + { typeof(DateTimeOffset), index => new DateTimeOffset(DateTime.Now) }, + { typeof(DBNull), index => DBNull.Value }, + { typeof(Decimal), index => (Decimal)index }, + { typeof(Double), index => (Double)(index + 0.1) }, + { typeof(Guid), index => Guid.NewGuid() }, + { typeof(Int16), index => (Int16)(index % Int16.MaxValue) }, + { typeof(Int32), index => (Int32)(index % Int32.MaxValue) }, + { typeof(Int64), index => (Int64)index }, + { typeof(Object), index => new object() }, + { typeof(SByte), index => (SByte)64 }, + { typeof(Single), index => (Single)(index + 0.1) }, + { + typeof(String), index => + { + return String.Format(CultureInfo.CurrentCulture, "sample string {0}", index); + } + }, + { + typeof(TimeSpan), index => + { + return TimeSpan.FromTicks(1234567); + } + }, + { typeof(UInt16), index => (UInt16)(index % UInt16.MaxValue) }, + { typeof(UInt32), index => (UInt32)(index % UInt32.MaxValue) }, + { typeof(UInt64), index => (UInt64)index }, + { + typeof(Uri), index => + { + return new Uri(String.Format(CultureInfo.CurrentCulture, "http://webapihelppage{0}.com", index)); + } + }, + }; + } + + public static bool CanGenerateObject(Type type) + { + return DefaultGenerators.ContainsKey(type); + } + + public object GenerateObject(Type type) + { + return DefaultGenerators[type](++_index); + } + } + } +} \ No newline at end of file diff --git a/WebApp/Areas/HelpPage/SampleGeneration/SampleDirection.cs b/WebApp/Areas/HelpPage/SampleGeneration/SampleDirection.cs new file mode 100644 index 0000000..fa027ae --- /dev/null +++ b/WebApp/Areas/HelpPage/SampleGeneration/SampleDirection.cs @@ -0,0 +1,11 @@ +namespace WebApp.Areas.HelpPage +{ + /// + /// Indicates whether the sample is used for request or response + /// + public enum SampleDirection + { + Request = 0, + Response + } +} \ No newline at end of file diff --git a/WebApp/Areas/HelpPage/SampleGeneration/TextSample.cs b/WebApp/Areas/HelpPage/SampleGeneration/TextSample.cs new file mode 100644 index 0000000..f004b7b --- /dev/null +++ b/WebApp/Areas/HelpPage/SampleGeneration/TextSample.cs @@ -0,0 +1,37 @@ +using System; + +namespace WebApp.Areas.HelpPage +{ + /// + /// This represents a preformatted text sample on the help page. There's a display template named TextSample associated with this class. + /// + public class TextSample + { + public TextSample(string text) + { + if (text == null) + { + throw new ArgumentNullException("text"); + } + Text = text; + } + + public string Text { get; private set; } + + public override bool Equals(object obj) + { + TextSample other = obj as TextSample; + return other != null && Text == other.Text; + } + + public override int GetHashCode() + { + return Text.GetHashCode(); + } + + public override string ToString() + { + return Text; + } + } +} \ No newline at end of file diff --git a/WebApp/Areas/HelpPage/Views/Help/Api.cshtml b/WebApp/Areas/HelpPage/Views/Help/Api.cshtml new file mode 100644 index 0000000..f0f791d --- /dev/null +++ b/WebApp/Areas/HelpPage/Views/Help/Api.cshtml @@ -0,0 +1,22 @@ +@using System.Web.Http +@using WebApp.Areas.HelpPage.Models +@model HelpPageApiModel + +@{ + var description = Model.ApiDescription; + ViewBag.Title = description.HttpMethod.Method + " " + description.RelativePath; +} + + +
+ +
+ @Html.DisplayForModel() +
+
diff --git a/WebApp/Areas/HelpPage/Views/Help/DisplayTemplates/ApiGroup.cshtml b/WebApp/Areas/HelpPage/Views/Help/DisplayTemplates/ApiGroup.cshtml new file mode 100644 index 0000000..b297f31 --- /dev/null +++ b/WebApp/Areas/HelpPage/Views/Help/DisplayTemplates/ApiGroup.cshtml @@ -0,0 +1,41 @@ +@using System.Web.Http +@using System.Web.Http.Controllers +@using System.Web.Http.Description +@using WebApp.Areas.HelpPage +@using WebApp.Areas.HelpPage.Models +@model IGrouping + +@{ + var controllerDocumentation = ViewBag.DocumentationProvider != null ? + ViewBag.DocumentationProvider.GetDocumentation(Model.Key) : + null; +} + +

@Model.Key.ControllerName

+@if (!String.IsNullOrEmpty(controllerDocumentation)) +{ +

@controllerDocumentation

+} + + + + + + @foreach (var api in Model) + { + + + + + } + +
APIDescription
@api.HttpMethod.Method @api.RelativePath + @if (api.Documentation != null) + { +

@api.Documentation

+ } + else + { +

No documentation available.

+ } +
\ No newline at end of file diff --git a/WebApp/Areas/HelpPage/Views/Help/DisplayTemplates/CollectionModelDescription.cshtml b/WebApp/Areas/HelpPage/Views/Help/DisplayTemplates/CollectionModelDescription.cshtml new file mode 100644 index 0000000..5f7525c --- /dev/null +++ b/WebApp/Areas/HelpPage/Views/Help/DisplayTemplates/CollectionModelDescription.cshtml @@ -0,0 +1,6 @@ +@using WebApp.Areas.HelpPage.ModelDescriptions +@model CollectionModelDescription +@if (Model.ElementDescription is ComplexTypeModelDescription) +{ + @Html.DisplayFor(m => m.ElementDescription) +} \ No newline at end of file diff --git a/WebApp/Areas/HelpPage/Views/Help/DisplayTemplates/ComplexTypeModelDescription.cshtml b/WebApp/Areas/HelpPage/Views/Help/DisplayTemplates/ComplexTypeModelDescription.cshtml new file mode 100644 index 0000000..68a4ca2 --- /dev/null +++ b/WebApp/Areas/HelpPage/Views/Help/DisplayTemplates/ComplexTypeModelDescription.cshtml @@ -0,0 +1,3 @@ +@using WebApp.Areas.HelpPage.ModelDescriptions +@model ComplexTypeModelDescription +@Html.DisplayFor(m => m.Properties, "Parameters") \ No newline at end of file diff --git a/WebApp/Areas/HelpPage/Views/Help/DisplayTemplates/DictionaryModelDescription.cshtml b/WebApp/Areas/HelpPage/Views/Help/DisplayTemplates/DictionaryModelDescription.cshtml new file mode 100644 index 0000000..e01f6f8 --- /dev/null +++ b/WebApp/Areas/HelpPage/Views/Help/DisplayTemplates/DictionaryModelDescription.cshtml @@ -0,0 +1,4 @@ +@using WebApp.Areas.HelpPage.ModelDescriptions +@model DictionaryModelDescription +Dictionary of @Html.DisplayFor(m => Model.KeyModelDescription.ModelType, "ModelDescriptionLink", new { modelDescription = Model.KeyModelDescription }) [key] +and @Html.DisplayFor(m => Model.ValueModelDescription.ModelType, "ModelDescriptionLink", new { modelDescription = Model.ValueModelDescription }) [value] \ No newline at end of file diff --git a/WebApp/Areas/HelpPage/Views/Help/DisplayTemplates/EnumTypeModelDescription.cshtml b/WebApp/Areas/HelpPage/Views/Help/DisplayTemplates/EnumTypeModelDescription.cshtml new file mode 100644 index 0000000..89e4cd8 --- /dev/null +++ b/WebApp/Areas/HelpPage/Views/Help/DisplayTemplates/EnumTypeModelDescription.cshtml @@ -0,0 +1,24 @@ +@using WebApp.Areas.HelpPage.ModelDescriptions +@model EnumTypeModelDescription + +

Possible enumeration values:

+ + + + + + + @foreach (EnumValueDescription value in Model.Values) + { + + + + + + } + +
NameValueDescription
@value.Name +

@value.Value

+
+

@value.Documentation

+
\ No newline at end of file diff --git a/WebApp/Areas/HelpPage/Views/Help/DisplayTemplates/HelpPageApiModel.cshtml b/WebApp/Areas/HelpPage/Views/Help/DisplayTemplates/HelpPageApiModel.cshtml new file mode 100644 index 0000000..077e225 --- /dev/null +++ b/WebApp/Areas/HelpPage/Views/Help/DisplayTemplates/HelpPageApiModel.cshtml @@ -0,0 +1,67 @@ +@using System.Web.Http +@using System.Web.Http.Description +@using WebApp.Areas.HelpPage.Models +@using WebApp.Areas.HelpPage.ModelDescriptions +@model HelpPageApiModel + +@{ + ApiDescription description = Model.ApiDescription; +} +

@description.HttpMethod.Method @description.RelativePath

+
+

@description.Documentation

+ +

Request Information

+ +

URI Parameters

+ @Html.DisplayFor(m => m.UriParameters, "Parameters") + +

Body Parameters

+ +

@Model.RequestDocumentation

+ + @if (Model.RequestModelDescription != null) + { + @Html.DisplayFor(m => m.RequestModelDescription.ModelType, "ModelDescriptionLink", new { modelDescription = Model.RequestModelDescription }) + if (Model.RequestBodyParameters != null) + { + @Html.DisplayFor(m => m.RequestBodyParameters, "Parameters") + } + } + else + { +

None.

+ } + + @if (Model.SampleRequests.Count > 0) + { +

Request Formats

+ @Html.DisplayFor(m => m.SampleRequests, "Samples") + } + +

Response Information

+ +

Resource Description

+ +

@description.ResponseDescription.Documentation

+ + @if (Model.ResourceDescription != null) + { + @Html.DisplayFor(m => m.ResourceDescription.ModelType, "ModelDescriptionLink", new { modelDescription = Model.ResourceDescription }) + if (Model.ResourceProperties != null) + { + @Html.DisplayFor(m => m.ResourceProperties, "Parameters") + } + } + else + { +

None.

+ } + + @if (Model.SampleResponses.Count > 0) + { +

Response Formats

+ @Html.DisplayFor(m => m.SampleResponses, "Samples") + } + +
\ No newline at end of file diff --git a/WebApp/Areas/HelpPage/Views/Help/DisplayTemplates/ImageSample.cshtml b/WebApp/Areas/HelpPage/Views/Help/DisplayTemplates/ImageSample.cshtml new file mode 100644 index 0000000..49e6c0e --- /dev/null +++ b/WebApp/Areas/HelpPage/Views/Help/DisplayTemplates/ImageSample.cshtml @@ -0,0 +1,4 @@ +@using WebApp.Areas.HelpPage +@model ImageSample + + \ No newline at end of file diff --git a/WebApp/Areas/HelpPage/Views/Help/DisplayTemplates/InvalidSample.cshtml b/WebApp/Areas/HelpPage/Views/Help/DisplayTemplates/InvalidSample.cshtml new file mode 100644 index 0000000..4573989 --- /dev/null +++ b/WebApp/Areas/HelpPage/Views/Help/DisplayTemplates/InvalidSample.cshtml @@ -0,0 +1,13 @@ +@using WebApp.Areas.HelpPage +@model InvalidSample + +@if (HttpContext.Current.IsDebuggingEnabled) +{ +
+

@Model.ErrorMessage

+
+} +else +{ +

Sample not available.

+} \ No newline at end of file diff --git a/WebApp/Areas/HelpPage/Views/Help/DisplayTemplates/KeyValuePairModelDescription.cshtml b/WebApp/Areas/HelpPage/Views/Help/DisplayTemplates/KeyValuePairModelDescription.cshtml new file mode 100644 index 0000000..b46f55b --- /dev/null +++ b/WebApp/Areas/HelpPage/Views/Help/DisplayTemplates/KeyValuePairModelDescription.cshtml @@ -0,0 +1,4 @@ +@using WebApp.Areas.HelpPage.ModelDescriptions +@model KeyValuePairModelDescription +Pair of @Html.DisplayFor(m => Model.KeyModelDescription.ModelType, "ModelDescriptionLink", new { modelDescription = Model.KeyModelDescription }) [key] +and @Html.DisplayFor(m => Model.ValueModelDescription.ModelType, "ModelDescriptionLink", new { modelDescription = Model.ValueModelDescription }) [value] \ No newline at end of file diff --git a/WebApp/Areas/HelpPage/Views/Help/DisplayTemplates/ModelDescriptionLink.cshtml b/WebApp/Areas/HelpPage/Views/Help/DisplayTemplates/ModelDescriptionLink.cshtml new file mode 100644 index 0000000..2be6105 --- /dev/null +++ b/WebApp/Areas/HelpPage/Views/Help/DisplayTemplates/ModelDescriptionLink.cshtml @@ -0,0 +1,26 @@ +@using WebApp.Areas.HelpPage.ModelDescriptions +@model Type +@{ + ModelDescription modelDescription = ViewBag.modelDescription; + if (modelDescription is ComplexTypeModelDescription || modelDescription is EnumTypeModelDescription) + { + if (Model == typeof(Object)) + { + @:Object + } + else + { + @Html.ActionLink(modelDescription.Name, "ResourceModel", "Help", new { modelName = modelDescription.Name }, null) + } + } + else if (modelDescription is CollectionModelDescription) + { + var collectionDescription = modelDescription as CollectionModelDescription; + var elementDescription = collectionDescription.ElementDescription; + @:Collection of @Html.DisplayFor(m => elementDescription.ModelType, "ModelDescriptionLink", new { modelDescription = elementDescription }) + } + else + { + @Html.DisplayFor(m => modelDescription) + } +} \ No newline at end of file diff --git a/WebApp/Areas/HelpPage/Views/Help/DisplayTemplates/Parameters.cshtml b/WebApp/Areas/HelpPage/Views/Help/DisplayTemplates/Parameters.cshtml new file mode 100644 index 0000000..7eeae9f --- /dev/null +++ b/WebApp/Areas/HelpPage/Views/Help/DisplayTemplates/Parameters.cshtml @@ -0,0 +1,48 @@ +@using System.Collections.Generic +@using System.Collections.ObjectModel +@using System.Web.Http.Description +@using System.Threading +@using WebApp.Areas.HelpPage.ModelDescriptions +@model IList + +@if (Model.Count > 0) +{ + + + + + + @foreach (ParameterDescription parameter in Model) + { + ModelDescription modelDescription = parameter.TypeDescription; + + + + + + + } + +
NameDescriptionTypeAdditional information
@parameter.Name +

@parameter.Documentation

+
+ @Html.DisplayFor(m => modelDescription.ModelType, "ModelDescriptionLink", new { modelDescription = modelDescription }) + + @if (parameter.Annotations.Count > 0) + { + foreach (var annotation in parameter.Annotations) + { +

@annotation.Documentation

+ } + } + else + { +

None.

+ } +
+} +else +{ +

None.

+} + diff --git a/WebApp/Areas/HelpPage/Views/Help/DisplayTemplates/Samples.cshtml b/WebApp/Areas/HelpPage/Views/Help/DisplayTemplates/Samples.cshtml new file mode 100644 index 0000000..c19596f --- /dev/null +++ b/WebApp/Areas/HelpPage/Views/Help/DisplayTemplates/Samples.cshtml @@ -0,0 +1,30 @@ +@using System.Net.Http.Headers +@model Dictionary + +@{ + // Group the samples into a single tab if they are the same. + Dictionary samples = Model.GroupBy(pair => pair.Value).ToDictionary( + pair => String.Join(", ", pair.Select(m => m.Key.ToString()).ToArray()), + pair => pair.Key); + var mediaTypes = samples.Keys; +} +
+ @foreach (var mediaType in mediaTypes) + { +

@mediaType

+
+ Sample: + @{ + var sample = samples[mediaType]; + if (sample == null) + { +

Sample not available.

+ } + else + { + @Html.DisplayFor(s => sample); + } + } +
+ } +
\ No newline at end of file diff --git a/WebApp/Areas/HelpPage/Views/Help/DisplayTemplates/SimpleTypeModelDescription.cshtml b/WebApp/Areas/HelpPage/Views/Help/DisplayTemplates/SimpleTypeModelDescription.cshtml new file mode 100644 index 0000000..3564bf0 --- /dev/null +++ b/WebApp/Areas/HelpPage/Views/Help/DisplayTemplates/SimpleTypeModelDescription.cshtml @@ -0,0 +1,3 @@ +@using WebApp.Areas.HelpPage.ModelDescriptions +@model SimpleTypeModelDescription +@Model.Documentation \ No newline at end of file diff --git a/WebApp/Areas/HelpPage/Views/Help/DisplayTemplates/TextSample.cshtml b/WebApp/Areas/HelpPage/Views/Help/DisplayTemplates/TextSample.cshtml new file mode 100644 index 0000000..6611c0e --- /dev/null +++ b/WebApp/Areas/HelpPage/Views/Help/DisplayTemplates/TextSample.cshtml @@ -0,0 +1,6 @@ +@using WebApp.Areas.HelpPage +@model TextSample + +
+@Model.Text
+
\ No newline at end of file diff --git a/WebApp/Areas/HelpPage/Views/Help/Index.cshtml b/WebApp/Areas/HelpPage/Views/Help/Index.cshtml new file mode 100644 index 0000000..4272e81 --- /dev/null +++ b/WebApp/Areas/HelpPage/Views/Help/Index.cshtml @@ -0,0 +1,38 @@ +@using System.Web.Http +@using System.Web.Http.Controllers +@using System.Web.Http.Description +@using System.Collections.ObjectModel +@using WebApp.Areas.HelpPage.Models +@model Collection + +@{ + ViewBag.Title = "ASP.NET Web API Help Page"; + + // Group APIs by controller + ILookup apiGroups = Model.ToLookup(api => api.ActionDescriptor.ControllerDescriptor); +} + + +
+
+
+

@ViewBag.Title

+
+
+
+
+ +
+ @foreach (var group in apiGroups) + { + @Html.DisplayFor(m => group, "ApiGroup") + } +
+
diff --git a/WebApp/Areas/HelpPage/Views/Help/ResourceModel.cshtml b/WebApp/Areas/HelpPage/Views/Help/ResourceModel.cshtml new file mode 100644 index 0000000..d626bfe --- /dev/null +++ b/WebApp/Areas/HelpPage/Views/Help/ResourceModel.cshtml @@ -0,0 +1,19 @@ +@using System.Web.Http +@using WebApp.Areas.HelpPage.ModelDescriptions +@model ModelDescription + + +
+ +

@Model.Name

+

@Model.Documentation

+
+ @Html.DisplayFor(m => Model) +
+
diff --git a/WebApp/Areas/HelpPage/Views/Shared/_Layout.cshtml b/WebApp/Areas/HelpPage/Views/Shared/_Layout.cshtml new file mode 100644 index 0000000..896c833 --- /dev/null +++ b/WebApp/Areas/HelpPage/Views/Shared/_Layout.cshtml @@ -0,0 +1,12 @@ + + + + + + @ViewBag.Title + @RenderSection("scripts", required: false) + + + @RenderBody() + + \ No newline at end of file diff --git a/WebApp/Areas/HelpPage/Views/Web.config b/WebApp/Areas/HelpPage/Views/Web.config new file mode 100644 index 0000000..0971732 --- /dev/null +++ b/WebApp/Areas/HelpPage/Views/Web.config @@ -0,0 +1,41 @@ + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/WebApp/Areas/HelpPage/Views/_ViewStart.cshtml b/WebApp/Areas/HelpPage/Views/_ViewStart.cshtml new file mode 100644 index 0000000..a925950 --- /dev/null +++ b/WebApp/Areas/HelpPage/Views/_ViewStart.cshtml @@ -0,0 +1,4 @@ +@{ + // Change the Layout path below to blend the look and feel of the help page with your existing web pages. + Layout = "~/Areas/HelpPage/Views/Shared/_Layout.cshtml"; +} \ No newline at end of file diff --git a/WebApp/Areas/HelpPage/XmlDocumentationProvider.cs b/WebApp/Areas/HelpPage/XmlDocumentationProvider.cs new file mode 100644 index 0000000..28efd04 --- /dev/null +++ b/WebApp/Areas/HelpPage/XmlDocumentationProvider.cs @@ -0,0 +1,161 @@ +using System; +using System.Globalization; +using System.Linq; +using System.Reflection; +using System.Web.Http.Controllers; +using System.Web.Http.Description; +using System.Xml.XPath; +using WebApp.Areas.HelpPage.ModelDescriptions; + +namespace WebApp.Areas.HelpPage +{ + /// + /// A custom that reads the API documentation from an XML documentation file. + /// + public class XmlDocumentationProvider : IDocumentationProvider, IModelDocumentationProvider + { + private XPathNavigator _documentNavigator; + private const string TypeExpression = "/doc/members/member[@name='T:{0}']"; + private const string MethodExpression = "/doc/members/member[@name='M:{0}']"; + private const string PropertyExpression = "/doc/members/member[@name='P:{0}']"; + private const string FieldExpression = "/doc/members/member[@name='F:{0}']"; + private const string ParameterExpression = "param[@name='{0}']"; + + /// + /// Initializes a new instance of the class. + /// + /// The physical path to XML document. + public XmlDocumentationProvider(string documentPath) + { + if (documentPath == null) + { + throw new ArgumentNullException("documentPath"); + } + XPathDocument xpath = new XPathDocument(documentPath); + _documentNavigator = xpath.CreateNavigator(); + } + + public string GetDocumentation(HttpControllerDescriptor controllerDescriptor) + { + XPathNavigator typeNode = GetTypeNode(controllerDescriptor.ControllerType); + return GetTagValue(typeNode, "summary"); + } + + public virtual string GetDocumentation(HttpActionDescriptor actionDescriptor) + { + XPathNavigator methodNode = GetMethodNode(actionDescriptor); + return GetTagValue(methodNode, "summary"); + } + + public virtual string GetDocumentation(HttpParameterDescriptor parameterDescriptor) + { + ReflectedHttpParameterDescriptor reflectedParameterDescriptor = parameterDescriptor as ReflectedHttpParameterDescriptor; + if (reflectedParameterDescriptor != null) + { + XPathNavigator methodNode = GetMethodNode(reflectedParameterDescriptor.ActionDescriptor); + if (methodNode != null) + { + string parameterName = reflectedParameterDescriptor.ParameterInfo.Name; + XPathNavigator parameterNode = methodNode.SelectSingleNode(String.Format(CultureInfo.InvariantCulture, ParameterExpression, parameterName)); + if (parameterNode != null) + { + return parameterNode.Value.Trim(); + } + } + } + + return null; + } + + public string GetResponseDocumentation(HttpActionDescriptor actionDescriptor) + { + XPathNavigator methodNode = GetMethodNode(actionDescriptor); + return GetTagValue(methodNode, "returns"); + } + + public string GetDocumentation(MemberInfo member) + { + string memberName = String.Format(CultureInfo.InvariantCulture, "{0}.{1}", GetTypeName(member.DeclaringType), member.Name); + string expression = member.MemberType == MemberTypes.Field ? FieldExpression : PropertyExpression; + string selectExpression = String.Format(CultureInfo.InvariantCulture, expression, memberName); + XPathNavigator propertyNode = _documentNavigator.SelectSingleNode(selectExpression); + return GetTagValue(propertyNode, "summary"); + } + + public string GetDocumentation(Type type) + { + XPathNavigator typeNode = GetTypeNode(type); + return GetTagValue(typeNode, "summary"); + } + + private XPathNavigator GetMethodNode(HttpActionDescriptor actionDescriptor) + { + ReflectedHttpActionDescriptor reflectedActionDescriptor = actionDescriptor as ReflectedHttpActionDescriptor; + if (reflectedActionDescriptor != null) + { + string selectExpression = String.Format(CultureInfo.InvariantCulture, MethodExpression, GetMemberName(reflectedActionDescriptor.MethodInfo)); + return _documentNavigator.SelectSingleNode(selectExpression); + } + + return null; + } + + private static string GetMemberName(MethodInfo method) + { + string name = String.Format(CultureInfo.InvariantCulture, "{0}.{1}", GetTypeName(method.DeclaringType), method.Name); + ParameterInfo[] parameters = method.GetParameters(); + if (parameters.Length != 0) + { + string[] parameterTypeNames = parameters.Select(param => GetTypeName(param.ParameterType)).ToArray(); + name += String.Format(CultureInfo.InvariantCulture, "({0})", String.Join(",", parameterTypeNames)); + } + + return name; + } + + private static string GetTagValue(XPathNavigator parentNode, string tagName) + { + if (parentNode != null) + { + XPathNavigator node = parentNode.SelectSingleNode(tagName); + if (node != null) + { + return node.Value.Trim(); + } + } + + return null; + } + + private XPathNavigator GetTypeNode(Type type) + { + string controllerTypeName = GetTypeName(type); + string selectExpression = String.Format(CultureInfo.InvariantCulture, TypeExpression, controllerTypeName); + return _documentNavigator.SelectSingleNode(selectExpression); + } + + private static string GetTypeName(Type type) + { + string name = type.FullName; + if (type.IsGenericType) + { + // Format the generic type name to something like: Generic{System.Int32,System.String} + Type genericType = type.GetGenericTypeDefinition(); + Type[] genericArguments = type.GetGenericArguments(); + string genericTypeName = genericType.FullName; + + // Trim the generic parameter counts from the name + genericTypeName = genericTypeName.Substring(0, genericTypeName.IndexOf('`')); + string[] argumentTypeNames = genericArguments.Select(t => GetTypeName(t)).ToArray(); + name = String.Format(CultureInfo.InvariantCulture, "{0}{{{1}}}", genericTypeName, String.Join(",", argumentTypeNames)); + } + if (type.IsNested) + { + // Changing the nested type name from OuterType+InnerType to OuterType.InnerType to match the XML documentation syntax. + name = name.Replace("+", "."); + } + + return name; + } + } +} diff --git a/WebApp/Controllers/CommentController.cs b/WebApp/Controllers/CommentController.cs index 0583210..11ac417 100644 --- a/WebApp/Controllers/CommentController.cs +++ b/WebApp/Controllers/CommentController.cs @@ -7,72 +7,47 @@ using System.Web.Mvc; using MongoDB.Bson; using System.Configuration; +using ControllerLogic; +using ControllersLogic.Interfaces; namespace WebApp.Controllers { public class CommentController : Controller { - private DB.Interfaces.ICommentRepository _commentRepository = new DB.Repositories.DBCommentRepository(); + private static ICommentLogic _commentLogic = new CommentLogic(); + [HttpPost, Route("Comment")] public JsonResult AddComment(String text, String name) - { - Comment comment = new Comment(); - comment.Text = text; - comment.CreationelData = DateTime.UtcNow; - comment.Name = name; - comment.Version = 1; - var id = _commentRepository.AddComment(comment).Id.ToString(); - return Json(new { Result = id }, JsonRequestBehavior.AllowGet); + { + return Json(new { Result = _commentLogic.AddComment(text, name) }, JsonRequestBehavior.AllowGet); } [HttpGet, Route("Comment/id{id}")] public JsonResult GetById(String id) { - var objectId = new ObjectId(); - if (!ObjectId.TryParse(id, out objectId)) - { - return Json(new { Result = "Bad id it's not objectId" }, JsonRequestBehavior.AllowGet); - } - if (objectId == null) - { - return Json(new { Result = "Bad id" }, JsonRequestBehavior.AllowGet); + try { + return Json(_commentLogic.GetById(id), JsonRequestBehavior.AllowGet); } - var comments = CommentWithoutObjectId.CommentToCommentWithoutObjectId(_commentRepository.GetCommentById(objectId)); - if (comments == null) + catch (Exception e) { - return Json(new { Result = "Bad id" }, JsonRequestBehavior.AllowGet); + return Json(e.ToString() , JsonRequestBehavior.AllowGet); } - return Json(comments, JsonRequestBehavior.AllowGet); } [HttpGet, Route("Comment")] public JsonResult Index() { - var comments = CommentWithoutObjectId.CommentsToCommentWithoutObjectId(_commentRepository.GetAll()); - return Json(comments, JsonRequestBehavior.AllowGet); + return Json(_commentLogic.GetAllComment(), JsonRequestBehavior.AllowGet); } [HttpPut, Route("Comment/id{id}")] public JsonResult UpdateById(String id, String name, String text) { - var objectId = new ObjectId(); - if (!ObjectId.TryParse(id, out objectId)) - { - return Json(new { Result = "Bad id it's not objectId" }, JsonRequestBehavior.AllowGet); - } - if (objectId == null) - { - return Json(new { Result = "Bad id" }, JsonRequestBehavior.AllowGet); + try + { + return Json(_commentLogic.UpdateById(id, name, text), JsonRequestBehavior.AllowGet); } - var comment = _commentRepository.GetCommentById(objectId); - if (comment == null) + catch (Exception e) { - return Json(new { Result = "Bad id" }, JsonRequestBehavior.AllowGet); + return Json(e.ToString() , JsonRequestBehavior.AllowGet); } - comment.Name = name; - comment.Text = text; - comment.Version++; - _commentRepository.DeleteById(objectId); - _commentRepository.AddComment(comment); - return Json(CommentWithoutObjectId.CommentToCommentWithoutObjectId(comment), JsonRequestBehavior.AllowGet); } - } } \ No newline at end of file diff --git a/WebApp/Controllers/CommentWebApiController.cs b/WebApp/Controllers/CommentWebApiController.cs new file mode 100644 index 0000000..7650d83 --- /dev/null +++ b/WebApp/Controllers/CommentWebApiController.cs @@ -0,0 +1,60 @@ +using ControllerLogic; +using ControllersLogic.Interfaces; +using DB.Models; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web; +using System.Web.Http; + +namespace WebApp.Controllers +{ + public class CommentWebApiController : ApiController + { + private static ICommentLogic _commentLogic = new CommentLogic(); + /// + /// Make new comment. + /// + /// Text + /// Name + /// Return id new comment + [HttpPost, Route("api/Comment")] + public String AddComment([FromBody]String text, [FromBody]String name) + { + return _commentLogic.AddComment(text, name); + } + /// + /// Get all comments + /// + /// all comments + [HttpGet, Route("api/Comment")] + public List GetAllComment() + { + return _commentLogic.GetAllComment(); + } + /// + /// Get comment by id + /// + /// Id + /// + [HttpGet, Route("api/Comment/id{id}")] + public CommentWithoutObjectId GetById(String id) + { + var comment = _commentLogic.GetById(id); + return comment; + } + /// + /// Update comment by id + /// + /// Id + /// New name + /// New text + /// New Comment + [HttpPut, Route("api/Comment/id{id}")] + public CommentWithoutObjectId UpdateById(String id, [FromBody]String name, [FromBody]String text) + { + var comment = _commentLogic.UpdateById(id, name, text); + return comment; + } + } +} \ No newline at end of file diff --git a/WebApp/Controllers/HomeController.cs b/WebApp/Controllers/HomeController.cs index d5976db..226e234 100644 --- a/WebApp/Controllers/HomeController.cs +++ b/WebApp/Controllers/HomeController.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Web; +using System.Web.Http; using System.Web.Mvc; namespace WebApp.Controllers diff --git a/WebApp/Controllers/ImageController.cs b/WebApp/Controllers/ImageController.cs index 4800460..f1fae3a 100644 --- a/WebApp/Controllers/ImageController.cs +++ b/WebApp/Controllers/ImageController.cs @@ -1,170 +1,88 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Web; using System.Web.Mvc; -using DB.Interfaces; using DB.Models; using MongoDB.Bson; -using MongoDB.Bson.IO; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using System.Web.Helpers; using System.IO; +using ControllersLogic.Logic; +using ControllersLogic.Interfaces; namespace WebApp.Controllers { public class ImageController:Controller { - private IImageRepository _imageRepository = new DB.Repositories.DBImageRepository(); - private ICommentRepository _commentRepository = new DB.Repositories.DBCommentRepository(); + private static IImageLogic _imageLogic = new ImageLogic(); [HttpPost, Route("Image/id{simageId}/comment")] public JsonResult AddCommentToImage(String simageId, String text, String name) - { - Comment comment = new Comment(); - comment.Text = text; - comment.CreationelData = DateTime.UtcNow; - comment.Name = name; - comment.Version = 1; - var commentId = _commentRepository.AddComment(comment).Id; - var imageId = new ObjectId(); - if (!ObjectId.TryParse(simageId, out imageId)) - { - return Json(new { Result = "Bad id it's not objectId" }, JsonRequestBehavior.AllowGet); - } - if (_imageRepository.GetImageById(imageId) == null) - { - var result = new { Result = "Bad id image" }; - return Json(result, JsonRequestBehavior.AllowGet); - } - _imageRepository.AddCommentToImage(commentId, imageId); - return Json(new { Result = commentId.ToString() }, JsonRequestBehavior.AllowGet); + { + return Json(new { Result = _imageLogic.AddCommentToImage(simageId, text, name) }, JsonRequestBehavior.AllowGet); } [HttpPost, Route("Image")] public JsonResult AddImage(String url, String name) - { - DB.Models.Image image = new DB.Models.Image(); - image.Url = url; - image.Version = 1; - image.Name = name; - image.CreationelData = DateTime.UtcNow; - var id = _imageRepository.AddImage(image).Id.ToString(); - return Json(new { Result = id }, JsonRequestBehavior.AllowGet); + { + return Json(new { Result = _imageLogic.AddImage(url, name) }, JsonRequestBehavior.AllowGet); } [HttpDelete, Route("Image/id{idImage}/comment/id{idComment}")] public JsonResult DeleteCommentFromImage(String idImage, String idComment) { - var objectIdImage = new ObjectId(); - var objectIdComment = new ObjectId(); - if(!ObjectId.TryParse(idImage, out objectIdImage)) - { - return Json(new { Result = "Bad image id" }, JsonRequestBehavior.AllowGet); - } - if(!ObjectId.TryParse(idComment, out objectIdComment)) - { - return Json(new { Result = "Bad comment id" }, JsonRequestBehavior.AllowGet); - } - if(_imageRepository.GetImageById(objectIdImage) == null) - { - return Json(new { Result = "Bad image id, not found in DB" }, JsonRequestBehavior.AllowGet); - } - _imageRepository.DeleteCommentFromImage(objectIdImage, objectIdComment); - _commentRepository.DeleteById(objectIdComment); - return Json(new { Result = "OK" }, JsonRequestBehavior.AllowGet); + return Json(new { Result = _imageLogic.DeleteCommentFromImage(idImage,idComment)}, JsonRequestBehavior.AllowGet); } [HttpPost, Route("Image/Download")] public JsonResult DownloadImage(HttpPostedFileBase uploadImage) { - if (uploadImage != null) { - string fileName = Path.GetFileName(uploadImage.FileName); - var input = uploadImage.InputStream; - CloudinaryDotNet.Account account = new CloudinaryDotNet.Account("hzvwvtbls", "482455376217895", "bXPz-CiQrEjZp4xqSV8UK_nfI2c"); - CloudinaryDotNet.Cloudinary cloudinary = new CloudinaryDotNet.Cloudinary(account); - CloudinaryDotNet.Actions.ImageUploadParams uploadParams = new CloudinaryDotNet.Actions.ImageUploadParams() - { - File = new CloudinaryDotNet.Actions.FileDescription(fileName, input) - }; - CloudinaryDotNet.Actions.ImageUploadResult uploadResult = cloudinary.Upload(uploadParams); - string url = cloudinary.Api.UrlImgUp.BuildUrl(String.Format("{0}.{1}", uploadResult.PublicId, uploadResult.Format)); - return Json(new { Result = url }, JsonRequestBehavior.AllowGet); - } - else - { - return Json(new { Result = "Bad file" }, JsonRequestBehavior.AllowGet); - } + return Json(new { Result = _imageLogic.DownloadImage(uploadImage) }, JsonRequestBehavior.AllowGet ); } [HttpGet, Route("Image/id{id}")] public JsonResult GetById(String id) { - var objectId = new ObjectId(); - if (!ObjectId.TryParse(id, out objectId)) - { - return Json(new { Result = "Bad id it's not objectId" }, JsonRequestBehavior.AllowGet); + try { + return Json(_imageLogic.GetById(id), JsonRequestBehavior.AllowGet ); } - if (objectId == null) + catch (Exception e) { - return Json(new { Result = "Bad id" }, JsonRequestBehavior.AllowGet); + return Json(e.ToString(), JsonRequestBehavior.AllowGet); } - if (_imageRepository.GetImageById(objectId) == null) + } + [HttpGet, Route("Image/id{id}/{version}")] + public JsonResult GetByIdAndVersion(String id, int version) + { + try { - var result = new List(); - result.Add(new { Result = "Bad id" }); - return Json(result, JsonRequestBehavior.AllowGet); + return Json(_imageLogic.GetByIdAndVersion(id, version), JsonRequestBehavior.AllowGet); } - var images = ImageWithoutObjectId.ImageToImageWithoutObjectId(_imageRepository.GetImageById(objectId)); - if (images == null) + catch (Exception e) { - return Json(new { Result = "Bad id" }, JsonRequestBehavior.AllowGet); + return Json(e.ToString(), JsonRequestBehavior.AllowGet); } - return Json(images, JsonRequestBehavior.AllowGet); } [HttpGet, Route("Image/id{simageId}/comment")] public JsonResult GetCommentFromImage(String simageId) { - var imageId = new ObjectId(); - if (!ObjectId.TryParse(simageId, out imageId)) - { - return Json(new { Result = "Bad id it's not objectId" }, JsonRequestBehavior.AllowGet); + try { + return Json(_imageLogic.GetCommentFromImage(simageId), JsonRequestBehavior.AllowGet); } - var image = _imageRepository.GetImageById(imageId); - if (image == null) + catch (Exception e) { - return Json(new { Result = "Bad id image, this image don't found in DB" }, JsonRequestBehavior.AllowGet); + return Json(e.ToString(), JsonRequestBehavior.AllowGet); } - - var movies = CommentWithoutObjectId.CommentsToCommentWithoutObjectId(_commentRepository.GetCommentsByIds(image.Comments)); - return Json(movies, JsonRequestBehavior.AllowGet); } [HttpGet, Route("Image")] public JsonResult Index() { - var images = ImageWithoutObjectId.ImagesToImageWithoutObjectId(_imageRepository.GetAllImage()); - return Json(images, JsonRequestBehavior.AllowGet); + return Json(_imageLogic.GetAllImage(), JsonRequestBehavior.AllowGet); } [HttpPut, Route("Image/id{id}")] public JsonResult UpdateById(String id, String name, String url) { - var objectId = new ObjectId(); - if (!ObjectId.TryParse(id, out objectId)) - { - return Json(new { Result = "Bad id it's not objectId" }, JsonRequestBehavior.AllowGet); - } - if (objectId == null) - { - return Json(new { Result = "Bad id" }, JsonRequestBehavior.AllowGet); + try { + return Json(_imageLogic.UpdateById(id, name, url), JsonRequestBehavior.AllowGet); } - var image = _imageRepository.GetImageById(objectId); - if (image == null) + catch (Exception e) { - return Json(new { Result = "Bad id" }, JsonRequestBehavior.AllowGet); + return Json(e.ToString(), JsonRequestBehavior.AllowGet); } - image.Url = url; - image.Version++; - image.Name = name; - _imageRepository.DeleteById(objectId); - _imageRepository.AddImage(image); - return Json(ImageWithoutObjectId.ImageToImageWithoutObjectId(image), JsonRequestBehavior.AllowGet); } } } \ No newline at end of file diff --git a/WebApp/Controllers/ImageWebApiController.cs b/WebApp/Controllers/ImageWebApiController.cs new file mode 100644 index 0000000..0b0845f --- /dev/null +++ b/WebApp/Controllers/ImageWebApiController.cs @@ -0,0 +1,111 @@ +using ControllersLogic.Interfaces; +using ControllersLogic.Logic; +using DB.Models; +using System; +using System.Collections.Generic; +using System.Web; +using System.Web.Http; + +namespace WebApp.Controllers +{ + public class ImageWebApiController:ApiController + { + private IImageLogic _imageLogic = new ImageLogic(); + /// + /// Add comment to image + /// + /// Image id + /// Comment text + /// Comment name + /// Id new comment + [HttpPost, Route("api/Image/id{simageId}/comment")] + public String AddCommentToImage(String simageId, [FromBody]String text, [FromBody]String name) + { + return _imageLogic.AddCommentToImage(simageId, text, name); + } + /// + /// Make new image + /// + /// Image url + /// Name + /// Id new image + [HttpPost, Route("api/Image")] + public String AddImage([FromBody]String url, [FromBody]String name) + { + return _imageLogic.AddImage(url, name); + } + /// + /// Delete comment from image + /// + /// Imgae id + /// Comment id + /// Result + [HttpDelete, Route("api/Image/id{idImage}/comment/id{idComment}")] + public String DeleteCommentFromImage(String idImage, String idComment) + { + return _imageLogic.DeleteCommentFromImage(idImage, idComment); + } + /// + /// Download image for getting url + /// + /// Uploded image, HttpPostedFileBase + /// Image url + [HttpPost, Route("api/Image/Download")] + public String DownloadImage([FromBody]HttpPostedFileBase uploadImage) + { + return _imageLogic.DownloadImage(uploadImage); + } + /// + /// Get all image + /// + /// List image + [HttpGet, Route("api/Image")] + public List GetAllImage() + { + return _imageLogic.GetAllImage(); + } + /// + /// Get image by id + /// + /// Id + /// Image + [HttpGet, Route("api/Image/id{id}")] + public ImageWithoutObjectId GetById(String id) + { + return _imageLogic.GetById(id); + } + /// + /// Get image by id and version + /// + /// Id + /// Version + /// Image + [HttpGet, Route("api/Image/id{id}/{version}")] + public ImageWithoutObjectId GetByIdAndVersion(String id, int version) + { + return _imageLogic.GetByIdAndVersion(id, version); + } + /// + /// Get comments from image + /// + /// Image id + /// List comments + [HttpGet, Route("api/Image/id{simageId}/comment")] + public List GetCommentsFromImage(String simageId) + { + return _imageLogic.GetCommentFromImage(simageId); + } + /// + /// Update image + /// + /// Id + /// New name + /// New url + /// New Image + [HttpPut, Route("api/Image/id{id}")] + public ImageWithoutObjectId UpdateById(String id, [FromBody]String name, [FromBody]String url) + { + return _imageLogic.UpdateById(id, name, url); + } + } +} \ No newline at end of file diff --git a/WebApp/Controllers/ProjectController.cs b/WebApp/Controllers/ProjectController.cs index ab8c6f0..57cb6c3 100644 --- a/WebApp/Controllers/ProjectController.cs +++ b/WebApp/Controllers/ProjectController.cs @@ -1,265 +1,116 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Web; using System.Web.Mvc; using MongoDB.Bson; using DB.Models; -using System.Web.Helpers; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using DB.Interfaces; +using ControllersLogic.Interfaces; +using ControllersLogic.Logic; namespace WebApp.Controllers { public class ProjectController : Controller { - private IProjectRepository _projectRepository = new DB.Repositories.DBProjectRepository(); - private IImageRepository _imageRepository = new DB.Repositories.DBImageRepository(); - private ICommentRepository _commentRepository = new DB.Repositories.DBCommentRepository(); + private IProjectLogic _projectLogic = new ProjectLogic(); [HttpPost, Route("Project/id{sprojectId}/Comment")] public JsonResult AddCommentToProject(String text, String name, String sprojectId) { - var projectId = new ObjectId(); - if (!ObjectId.TryParse(sprojectId, out projectId)) - { - var result = new { Result = "Bad id it's not objectId" }; - return Json(result, JsonRequestBehavior.AllowGet); - } - if (_projectRepository.GetProjectById(projectId) == null) - { - var result = new { Result = "Bad id it's not found in DB" }; - return Json(result, JsonRequestBehavior.AllowGet); - } - Comment comment = new Comment(); - comment.Text = text; - comment.CreationelData = DateTime.UtcNow; - comment.Name = name; - comment.Version = 1; - var commentId = _commentRepository.AddComment(comment).Id; - _projectRepository.AddCommentToProject(commentId, projectId); - return Json(new { Result = commentId.ToString() }, JsonRequestBehavior.AllowGet); + return Json(new { Result = _projectLogic.AddCommentToProject(text, name, sprojectId) }, JsonRequestBehavior.AllowGet); } [HttpPost, Route("Project/id{sprojectId}/Image")] public JsonResult AddImageToProject(String url, String name, String sprojectId) - { - var projectId = new ObjectId(); - if (!ObjectId.TryParse(sprojectId, out projectId)) - { - var result = new { Result = "Bad id it's not objectId" }; - return Json(result, JsonRequestBehavior.AllowGet); - } - if (_projectRepository.GetProjectById(projectId) == null) - { - var result = new { Result = "Bad id it's not found in DB" }; - return Json(result, JsonRequestBehavior.AllowGet); - } - Image image = new Image(); - image.Url = url; - image.Version = 1; - image.Name = name; - image.CreationelData = DateTime.UtcNow; - var imageId = _imageRepository.AddImage(image).Id; - _projectRepository.AddImageToProject(imageId, projectId); - return Json(new { Result = imageId.ToString() }, JsonRequestBehavior.AllowGet); + { + return Json(new { Result = _projectLogic.AddImageToProject(url, name, sprojectId) }, JsonRequestBehavior.AllowGet); } [HttpPost, Route("Project")] public JsonResult AddProject(String name) { - Project project = new Project(); - project.Name = name; - project.Version = 1; - project.CreationelData = DateTime.UtcNow; - var id = _projectRepository.AddProject(project).Id.ToString(); - return Json(new { Result = id }, JsonRequestBehavior.AllowGet); + return Json(new { Result = _projectLogic.AddProject(name) }, JsonRequestBehavior.AllowGet); } [HttpPost, Route("Project/id{sidRoot}/project")] public JsonResult AddProjectToProject(String sidRoot, String name) { - var idRoot = new ObjectId(); - if (!ObjectId.TryParse(sidRoot, out idRoot)) - { - var result = new { Result = "Bad id it's not objectId" }; - return Json(result, JsonRequestBehavior.AllowGet); - } - if (_projectRepository.GetProjectById(idRoot) == null) - { - var result = new { Result = "Bad id it's not found in DB" }; - return Json(result, JsonRequestBehavior.AllowGet); - } - Project project = new Project(); - project.Name = name; - project.Version = 1; - project.CreationelData = DateTime.UtcNow; - var idNew = _projectRepository.AddProject(project).Id; - _projectRepository.AddProjectToProject(idNew, idRoot); - return Json(new { Result = idNew.ToString() }, JsonRequestBehavior.AllowGet); + return Json(new { Result = _projectLogic.AddProjectToProject(sidRoot, name) }, JsonRequestBehavior.AllowGet); } [HttpDelete, Route("Project/id{id}")] public JsonResult DeleteById(String id) { - var objectId = new ObjectId(); - if (!ObjectId.TryParse(id, out objectId)) - { - return Json(new { Result = "Bad id it's not objectId" }, JsonRequestBehavior.AllowGet); - } - if (objectId == null) - { - return Json(new { Result = "Bad id" }, JsonRequestBehavior.AllowGet); - } - if (_projectRepository.GetProjectById(objectId) == null) - { - return Json(new { Result = "Bad id" }, JsonRequestBehavior.AllowGet); - } - _projectRepository.DeleteById(objectId); - return Json(new { Result = "OK" }, JsonRequestBehavior.AllowGet); + return Json(new { Result = _projectLogic.DeleteById(id) }, JsonRequestBehavior.AllowGet); } [HttpDelete, Route("Project/id{projectId}/comment/id{commentId}")] public JsonResult DeleteCommentFromProject(String projectId, String commentId) { - var objectIdProject = new ObjectId(); - var objectIdComment = new ObjectId(); - if (!ObjectId.TryParse(projectId, out objectIdProject)) - { - return Json(new { Result = "Bad id project it's not objectId" }, JsonRequestBehavior.AllowGet); - } - if (!ObjectId.TryParse(commentId, out objectIdComment)) - { - return Json(new { Result = "Bad id coomment it's not objectId" }, JsonRequestBehavior.AllowGet); - } - if (_projectRepository.GetProjectById(objectIdProject) == null) - { - return Json(new { Result = "Bad id" }, JsonRequestBehavior.AllowGet); - } - _projectRepository.DeleteCommentFromProject(objectIdProject, objectIdComment); - _commentRepository.DeleteById(objectIdComment); - return Json(new { Result = "OK" }, JsonRequestBehavior.AllowGet); + return Json(new { Result = _projectLogic.DeleteCommentFromProject(projectId, commentId)}, JsonRequestBehavior.AllowGet); } [HttpDelete, Route("Project/id{projectId}/image/id{imageId}")] public JsonResult DeleteImageFromProject(String projectId, String imageId) { - var objectIdProject = new ObjectId(); - var objectIdImage = new ObjectId(); - if (!ObjectId.TryParse(projectId, out objectIdProject)) - { - return Json(new { Result = "Bad id project it's not objectId" }, JsonRequestBehavior.AllowGet); - } - if (!ObjectId.TryParse(imageId, out objectIdImage)) - { - return Json(new { Result = "Bad id image it's not objectId" }, JsonRequestBehavior.AllowGet); - } - if (_projectRepository.GetProjectById(objectIdProject) == null) - { - return Json(new { Result = "Bad id" }, JsonRequestBehavior.AllowGet); - } - _projectRepository.DeleteImageFromProject(objectIdProject, objectIdImage); - _imageRepository.DeleteById(objectIdImage); - return Json(new { Result = "OK" }, JsonRequestBehavior.AllowGet); + return Json(new { Result = _projectLogic.DeleteImageFromProject(projectId, imageId) }, JsonRequestBehavior.AllowGet); } [HttpGet, Route("Project/id{id}")] public JsonResult GetById(String id) { - var objectId = new ObjectId(); - if (!ObjectId.TryParse(id, out objectId)) + try { - return Json(new { Result = "Bad id it's not objectId" }, JsonRequestBehavior.AllowGet); - } - if (objectId == null) - { - return Json(new { Result = "Bad id" }, JsonRequestBehavior.AllowGet); + return Json(_projectLogic.GetById(id), JsonRequestBehavior.AllowGet); } - var project = ProjectWithoutObjectId.ProjectToProjectWithoutObjectId(_projectRepository.GetProjectById(objectId)); - if (project == null) + catch (Exception e) { - return Json(new { Result = "Bad id" }, JsonRequestBehavior.AllowGet); + return Json(new { Result = e.ToString() }, JsonRequestBehavior.AllowGet); } - return Json(project, JsonRequestBehavior.AllowGet); } [HttpGet, Route("Project/id{sidRoot}/Comment")] public JsonResult GetCommentsFormProject(String sidRoot) { - var idRoot = new ObjectId(); - if (!ObjectId.TryParse(sidRoot, out idRoot)) + try { - var result = new List(); - result.Add(new { Result = "Bad id it's not objectId" }); - return Json(result, JsonRequestBehavior.AllowGet); + return Json(_projectLogic.GetCommentsFormProject(sidRoot), JsonRequestBehavior.AllowGet); } - var project = _projectRepository.GetProjectById(idRoot); - if (project == null) + catch (Exception e) { - var result = new List(); - result.Add(new { Result = "Bad id it's not found in DB" }); - return Json(result, JsonRequestBehavior.AllowGet); + return Json(new { Result = e.ToString() }, JsonRequestBehavior.AllowGet); } - var movies = CommentWithoutObjectId.CommentsToCommentWithoutObjectId(_commentRepository.GetCommentsByIds(project.Comments)); - return Json(movies, JsonRequestBehavior.AllowGet); } [HttpGet, Route("Project/id{sidRoot}/Image")] public JsonResult GetImagesFormProject(String sidRoot) { - var idRoot = new ObjectId(); - if (!ObjectId.TryParse(sidRoot, out idRoot)) + try { - var result = new List(); - result.Add(new { Result = "Bad id it's not objectId" }); - return Json(result, JsonRequestBehavior.AllowGet); + return Json(_projectLogic.GetImagesFormProject(sidRoot), JsonRequestBehavior.AllowGet); } - var project = _projectRepository.GetProjectById(idRoot); - if (project == null) + catch (Exception e) { - var result = new List(); - result.Add(new { Result = "Bad id it's not found in DB" }); - return Json(result, JsonRequestBehavior.AllowGet); + return Json(new { Result = e.ToString() }, JsonRequestBehavior.AllowGet); } - var movies = ImageWithoutObjectId.ImagesToImageWithoutObjectId(_imageRepository.GetImagesByIds(project.Images)); - return Json(movies, JsonRequestBehavior.AllowGet); } [HttpGet, Route("Project/id{sidRoot}/Project")] public JsonResult GetPtojectsFormProject(String sidRoot) { - var idRoot = new ObjectId(); - if (!ObjectId.TryParse(sidRoot, out idRoot)) - { - return Json(new { Result = "Bad id it's not objectId" }, JsonRequestBehavior.AllowGet); + try + { + return Json(_projectLogic.GetPtojectsFormProject(sidRoot), JsonRequestBehavior.AllowGet); } - var project = _projectRepository.GetProjectById(idRoot); - if (project == null) + catch (Exception e) { - return Json(new { Result = "Bad id it's not found in DB" }, JsonRequestBehavior.AllowGet); + return Json(new { Result = e.ToString() }, JsonRequestBehavior.AllowGet); } - var movies = ProjectWithoutObjectId.ProjectsToProjectWithoutObjectId(_projectRepository.GetProjectsByIds(project.Projects)); - return Json(movies, JsonRequestBehavior.AllowGet); } [HttpGet, Route("Project")] public JsonResult Index() { - var projects = ProjectWithoutObjectId.ProjectsToProjectWithoutObjectId(_projectRepository.GetAllProject()); - return Json(projects, JsonRequestBehavior.AllowGet); + return Json(_projectLogic.GetAllProjects(), JsonRequestBehavior.AllowGet); } [HttpPut, Route("Project/id{id}")] public JsonResult UpdateById(String id, String name) { - var objectId = new ObjectId(); - if (!ObjectId.TryParse(id, out objectId)) - { - return Json(new { Result = "Bad id it's not objectId" }, JsonRequestBehavior.AllowGet); - } - if (objectId == null) + try { - return Json(new { Result = "Bad id" }, JsonRequestBehavior.AllowGet); + return Json(_projectLogic.UpdateById(id, name), JsonRequestBehavior.AllowGet); } - var project = _projectRepository.GetProjectById(objectId); - if (project == null) + catch (Exception e) { - return Json(new { Result = "Bad id" }, JsonRequestBehavior.AllowGet); + return Json(new { Result = e.ToString() }, JsonRequestBehavior.AllowGet); } - project.Name = name; - project.Version++; - _projectRepository.DeleteById(objectId); - _projectRepository.AddProject(project); - return Json(ProjectWithoutObjectId.ProjectToProjectWithoutObjectId(project), JsonRequestBehavior.AllowGet); } } } \ No newline at end of file diff --git a/WebApp/Controllers/ProjectWebApiController.cs b/WebApp/Controllers/ProjectWebApiController.cs new file mode 100644 index 0000000..c3ba7de --- /dev/null +++ b/WebApp/Controllers/ProjectWebApiController.cs @@ -0,0 +1,151 @@ +using System.Web.Http; +using System; +using ControllersLogic.Interfaces; +using ControllersLogic.Logic; +using DB.Models; +using System.Collections.Generic; + +namespace WebApp.Controllers +{ + public class ProjectWebApiController:ApiController + { + private IProjectLogic _projectLogic = new ProjectLogic(); + /// + /// Add new comment to project + /// + /// Text + /// Name + /// Project id + /// Id new project + [HttpPost, Route("api/Project/id{sprojectId}/Comment")] + public String AddCommentToProject([FromBody]String text, [FromBody]String name, String sprojectId) + { + return _projectLogic.AddCommentToProject(text, name, sprojectId); + } + /// + /// Add new image to ptoject + /// + /// Image url + /// Name + /// Project id + /// Id new image + [HttpPost, Route("api/Project/id{sprojectId}/Image")] + public String AddImageToProject([FromBody]String url, [FromBody]String name, String sprojectId) + { + return _projectLogic.AddImageToProject(url, name, sprojectId); + } + /// + /// Add new project + /// + /// Name + /// Id new project + [HttpPost, Route("api/Project")] + public String AddProject([FromBody]String name) + { + return _projectLogic.AddProject(name); + } + /// + /// Add new project into ptoject + /// + /// Id root project + /// Name new project + /// Id new project + [HttpPost, Route("api/Project/id{sidRoot}/project")] + public String AddProjectToProject(String sidRoot, [FromBody]String name) + { + return _projectLogic.AddProjectToProject(sidRoot, name); + } + /// + /// Delete project by id + /// + /// Deleted project id + /// Result + [HttpDelete, Route("api/Project/id{id}")] + public String DeleteById(String id) + { + return _projectLogic.DeleteById(id); + } + /// + /// GetAllProject + /// + /// List project + [HttpGet, Route("api/Project")] + public List GetAllProject() + { + return _projectLogic.GetAllProjects(); + } + /// + /// Delete comment from project + /// + /// Project id + /// Comment id + /// Result + [HttpDelete, Route("api/Project/id{projectId}/comment/id{commentId}")] + public String DeleteCommentFromProject(String projectId, String commentId) + { + return _projectLogic.DeleteCommentFromProject(projectId, commentId); + } + /// + /// Delete image from project + /// + /// Project id + /// Image id + /// Result + [HttpDelete, Route("api/Project/id{projectId}/image/id{imageId}")] + public String DeleteImageFromProject(String projectId, String imageId) + { + return _projectLogic.DeleteImageFromProject(projectId, imageId); + } + /// + /// Get project by id + /// + /// Id + /// Project + [HttpGet, Route("api/Project/id{id}")] + public ProjectWithoutObjectId GetById(String id) + { + return _projectLogic.GetById(id); + } + /// + /// Get comment from project + /// + /// Id project + /// List id comments + [HttpGet, Route("api/Project/id{sidRoot}/Comment")] + public List GetCommentsFormProject(String sidRoot) + { + return _projectLogic.GetCommentsFormProject(sidRoot); + } + /// + /// Get image from project + /// + /// Id project + /// List id images + [HttpGet, Route("api/Project/id{sidRoot}/Image")] + public List GetImagesFormProject(String sidRoot) + { + return _projectLogic.GetImagesFormProject(sidRoot); + } + /// + /// Get project from project + /// + /// Id root project + /// List id projects + [HttpGet, Route("api/Project/id{sidRoot}/Project")] + public List GetPtojectsFormProject(String sidRoot) + { + return _projectLogic.GetPtojectsFormProject(sidRoot); + } + /// + /// Update project by id + /// + /// Id + /// New name + /// New project + [HttpPut, Route("api/Project/id{id}")] + public ProjectWithoutObjectId UpdateById(String id, [FromBody]String name) + { + return _projectLogic.UpdateById(id, name); + } + } +} \ No newline at end of file diff --git a/WebApp/Global.asax.cs b/WebApp/Global.asax.cs index 49b2841..44cac9d 100644 --- a/WebApp/Global.asax.cs +++ b/WebApp/Global.asax.cs @@ -2,9 +2,11 @@ using System.Collections.Generic; using System.Linq; using System.Web; +using System.Web.Http; using System.Web.Mvc; using System.Web.Optimization; using System.Web.Routing; +using WebApp.App_Start; namespace WebApp { @@ -13,7 +15,8 @@ public class MvcApplication : System.Web.HttpApplication protected void Application_Start() { AreaRegistration.RegisterAllAreas(); - FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); + FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); + GlobalConfiguration.Configure(WebApiConfig.Register); RouteConfig.RegisterRoutes(RouteTable.Routes); BundleConfig.RegisterBundles(BundleTable.Bundles); } diff --git a/WebApp/Models/IdentityModels.cs b/WebApp/Models/IdentityModels.cs index 48e9151..29ebcd9 100644 --- a/WebApp/Models/IdentityModels.cs +++ b/WebApp/Models/IdentityModels.cs @@ -17,24 +17,14 @@ public async Task GenerateUserIdentityAsync(UserManager { + public ApplicationDbContext() + : base("DefaultConnection", throwIfV1Schema: false) + { + } public static ApplicationDbContext Create() { return new ApplicationDbContext(); diff --git a/WebApp/Views/Home/About.cshtml b/WebApp/Views/Home/About.cshtml deleted file mode 100644 index 4b2d9e8..0000000 --- a/WebApp/Views/Home/About.cshtml +++ /dev/null @@ -1,7 +0,0 @@ -@{ - ViewBag.Title = "About"; -} -

@ViewBag.Title.

-

@ViewBag.Message

- -

Use this area to provide additional information.

diff --git a/WebApp/Views/Home/Index.cshtml b/WebApp/Views/Home/Index.cshtml index d682633..e4b4930 100644 --- a/WebApp/Views/Home/Index.cshtml +++ b/WebApp/Views/Home/Index.cshtml @@ -50,7 +50,7 @@
  • /project/id{id}/project -

    Получить все подпроекты к проекта

    +

    Получить все подпроекты к проекта

  • /project/id{id}/project @@ -59,6 +59,12 @@
  • /project/id{id}/image

    Получить все картинки из корня проекта

    +
  • /project/id{id}/image diff --git a/WebApp/Views/Home/Project.cshtml b/WebApp/Views/Home/Project.cshtml deleted file mode 100644 index 0f4327e..0000000 --- a/WebApp/Views/Home/Project.cshtml +++ /dev/null @@ -1,17 +0,0 @@ -@{ - ViewBag.Title = "Contact"; -} -

    @ViewBag.Title.

    -

    @ViewBag.Message

    - -
    - One Microsoft Way
    - Redmond, WA 98052-6399
    - P: - 425.555.0100 -
    - -
    - Support: Support@example.com
    - Marketing: Marketing@example.com -
    \ No newline at end of file diff --git a/WebApp/Views/Shared/_Layout.cshtml b/WebApp/Views/Shared/_Layout.cshtml index 8549837..f81c784 100644 --- a/WebApp/Views/Shared/_Layout.cshtml +++ b/WebApp/Views/Shared/_Layout.cshtml @@ -18,6 +18,7 @@ @Html.ActionLink("Ease-L", "Index", "Home", new { area = "" }, new { @class = "navbar-brand" }) + @Html.ActionLink("Documentation", "Index", "Help", new { area = "" }, new { @class = "navbar-brand" })