This is just a followup with two classes from the discussion forum. I haven’t tested the PostRepository class well yet, but I’ll update it with fixes later. Util is the general utilities class that I’ve used in previous projects. It’s basically for rudimentary formatting, input validation etc…
6:40 AM… Time for bed!
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Security.Cryptography;
using System.Globalization;
using System.Web;
namespace Road.Helpers
{
public class Util
{
/// <summary>
/// Gets the checksum of a local file or some text.
/// </summary>
/// <param name="source">Path to a file or a string</param>
/// <param name="mode">Checksum mode in sha1, sha256, sha512 or md5 (default)</param>
/// <param name="isFile">True if file mode or false for text mode (defaults to false)</param>
/// <returns>Completed checksum</returns>
public static string GetChecksum(string source, string mode = "md5", bool isFile = false)
{
byte[] bytes = { };
Stream fs;
if (isFile)
fs = new BufferedStream(File.OpenRead(source), 120000);
else
fs = new MemoryStream(Encoding.UTF8.GetBytes(source));
switch (mode.ToLower())
{
case "sha1":
using (SHA1CryptoServiceProvider sha1 =
new SHA1CryptoServiceProvider())
bytes = sha1.ComputeHash(fs);
break;
case "sha256":
using (SHA256CryptoServiceProvider sha256 =
new SHA256CryptoServiceProvider())
bytes = sha256.ComputeHash(fs);
break;
case "sha512":
using (SHA512CryptoServiceProvider sha512 =
new SHA512CryptoServiceProvider())
bytes = sha512.ComputeHash(fs);
break;
case "md5":
default:
using (MD5CryptoServiceProvider md5 =
new MD5CryptoServiceProvider())
bytes = md5.ComputeHash(fs);
break;
}
// Cleanup
fs.Close();
fs = null;
return BitConverter
.ToString(bytes)
.Replace("-", "")
.ToLower();
}
/// <summary>
/// Returns the page slug or converts a page title into a slug
/// </summary>
public static string GetSlug(string val, string d, int length = 45, bool lower = false)
{
val = Util.DefaultFlatString(val, d, length);
// Duplicate spaces
val = Regex.Replace(val, @"[\s-]+", " ").Trim();
val = Util.NormalizeString(val); // Remove special chars
val = Regex.Replace(val, @"\s", "-"); // Spaces to dashes
// If we still couldn't get a proper string, generate one from default
val = (String.IsNullOrEmpty(val) || val.Length < 3) ? d :
val.Substring(0, val.Length <= length ? val.Length : length).Trim();
return (lower) ? val.ToLower() : val;
}
private static string NormalizeString(string txt)
{
if (!String.IsNullOrEmpty(txt))
txt = txt.Normalize(NormalizationForm.FormD);
StringBuilder sb = new StringBuilder();
sb.Append(
txt.Normalize(NormalizationForm.FormD).Where(
c => CharUnicodeInfo.GetUnicodeCategory(c)
!= UnicodeCategory.NonSpacingMark).ToArray()
);
return sb.ToString().Normalize(NormalizationForm.FormD);
}
/// <summary>
/// Gets an array of cleaned tags
/// </summary>
/// <param name="txt">A comma delimited string of tags</param>
/// <returns>Array of cleaned tags</returns>
public static string[] GetTags(string txt, bool lower = false)
{
string[] tags = txt.Split(',');
ArrayList clean = new ArrayList();
for (int i = 0; i < tags.Length; i++)
{
tags[i] = DefaultFlatString(tags[i], " ").Trim();
if (!string.IsNullOrEmpty(tags[i]))
tags[i] = NormalizeString((lower)?
tags[i].ToLower() : tags[i]);
// Don't want to repeat
if (!clean.Contains(tags[i]))
clean.Add(tags[i]);
}
return (string[])clean.ToArray(typeof(string));
}
/// <summary>
/// Gets an array of cleaned keywords
/// </summary>
/// <param name="txt">A comma delimited string of keywords</param>
/// <param name="limit">Limit s the number of tags returned</param>
/// <param name="tolower">Optional parameter to convert the text to lowercase</param>
/// <returns>Array of cleaned keywords</returns>
public static List<string> GetKeywords(string txt, int limit, bool tolower = true)
{
string[] tags = txt.Split(',');
List<string> clean = new List<string>();
for (int i = 0; i < tags.Length; i++)
{
tags[i] = Util.DefaultFlatString(tags[i], "");
if (!String.IsNullOrEmpty(tags[i]))
{
if (tolower)
clean.Add(tags[i].ToLower());
else
clean.Add(tags[i]);
}
}
return clean;
}
/// <summary>
/// Shorten a give text block followed by an ellipse
/// </summary>
public static string TrimText(string strInput, int intNum)
{
strInput = strInput.Replace("\r", string.Empty)
.Replace("\n", string.Empty);
if ((strInput.Length > intNum) && (intNum > 0))
{
strInput = strInput.Substring(0, intNum) + "...";
}
return strInput;
}
/// <summary>
/// Checks whether string has value or sets default it doesn't or is at 0
/// </summary>
public static int DefaultInt(string val, int d, int? min)
{
int tmp = 0;
if (!Int32.TryParse(val, out tmp))
tmp = d;
if (min.HasValue)
if (tmp <= min.Value) tmp = d;
return tmp;
}
/// <summary>
/// Checks whether nullable int has value or sets default it doesn't or is at 0
/// </summary>
public static int DefaultInt(int? val, int d, int? min)
{
val = val ?? d;
if (min.HasValue)
if (val.Value <= min.Value) val = d;
return val.Value;
}
/// <summary>
/// Checks whether nullable bool has value or sets default it doesn't
/// </summary>
public static bool DefaultBool(bool? val, bool d)
{
val = val ?? d;
return val.Value;
}
/// <summary>
/// Checks whether nullable bool has value or sets default it doesn't
/// </summary>
public static bool DefaultBool(string val, bool d)
{
bool tmp = d;
if (Boolean.TryParse(val, out tmp))
return tmp;
return d;
}
/// <summary>
/// Returns a flat (no line breaks) string or a default value if empty
/// </summary>
public static string DefaultFlatString(string val, string d, int l = 255)
{
return Util.DefaultString(val, d, l).Replace(Environment.NewLine, "");
}
/// <summary>
/// Checks whether nullable string has value or sets default it doesn't or is at empty
/// </summary>
public static string DefaultString(string val, string d, int l = 255)
{
if (string.IsNullOrEmpty(val)) val = d;
val.Replace("\r", Environment.NewLine)
.Replace("\n", Environment.NewLine);
if ((val.Length > 0) && (val.Length > l))
val = val.Substring(0, l-1);
return val;
}
/// <summary>
/// Converts a string value to a DateTime object or returns the default value on failure
/// </summary>
public static DateTime DefaultDate(string val, DateTime d)
{
DateTime dt;
if (DateTime.TryParse(val, out dt))
return dt;
return d;
}
/// <summary>
/// Converts a nullable date value to a DateTime object or returns the default value on failure
/// </summary>
public static DateTime DefaultDate(DateTime? val, DateTime d)
{
DateTime dt;
dt = (val.HasValue) ? val.Value : d;
return d;
}
/// <summary>
/// Gets the current user's IP address
/// </summary>
public static string GetUserIP()
{
// Connecting through a proxy?
string ip = HttpContext.Current.Request.ServerVariables["HTTP_X_FORWARDED_FOR"];
// None found
if (string.IsNullOrEmpty(ip) || ip.ToLower() == "unknown")
ip = HttpContext.Current.Request.ServerVariables["REMOTE_ADDR"];
return ip;
}
public static string GetEmail(string v)
{
string email = @"^[\w!#$%&'*+\-/=?\^_`{|}~]+(\.[\w!#$%&'*+\-/=?\^_`{|}~]+)*" +
@"@((([\-\w]+\.)+[a-zA-Z]{2,4})|(([0-9]{1,3}\.){3}[0-9]{1,3}))$";
if (!string.IsNullOrEmpty(v))
if (Regex.IsMatch(v, email))
return v;
// Didn't match the email format, so sent a cleaned string
return Util.DefaultFlatString(v, "");
}
}
}
PostRepository. This one’s a bit long…
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Road.Helpers;
namespace Road.Models
{
public class PostRepository
{
// Common DataContext
private readonly CMDataContext db;
/// <summary>
/// Constructor
/// </summary>
/// <param name="context">Global context</param>
public PostRepository(CMDataContext context)
{
this.db = context;
}
#region Topic display methods
/// <summary>
/// Gets a topic by the given id and finds corresponding replies if any
/// </summary>
/// <param name="id">Topic id to search</param>
/// <param name="index">Current page index</param>
/// <param name="limit">Page size limit</param>
/// <param name="unapproved">Include unapproved replies (optional)</param>
/// <param name="status">Array of reply status types (optional)</param>
/// <param name="newestFirst">Sort by newest replies first (optional)</param>
public Topic TopicById(int id, int index, int limit,
bool unapproved = false, ReplyStatus[] status = null, bool newestFirst = false)
{
var query = from p in db.Posts
where p.PostId == id
select p;
Topic topic = TopicQuery(query).FirstOrDefault();
// We have a topic and replies were also requested
if (topic != null && limit > 0)
{
var rquery = from p in db.Posts
join pa in db.PostRelations on p.PostId equals pa.ParentId
where pa.ParentId == topic.Id
select p;
topic.Replies =
ReplyQuery(rquery, unapproved, status, newestFirst).ToPagedList(index, limit);
}
return topic;
}
/// <summary>
/// Gets a list of topics (most basic request, usually for frontpage)
/// </summary>
/// <param name="limit">Page size limit</param>
/// <param name="unapproved">Include unapproved topics (optional)</param>
/// <param name="status">Array of status topic status types (optional)</param>
/// <param name="newestFirst">Sort by newest topics first (optional)</param>
/// <returns>List of topics</returns>
public List<Topic> TopicList(int limit, bool unapproved = false,
TopicStatus[] status = null, bool newestFirst = true)
{
var query = from p in db.Posts
select p;
return TopicQuery(query, unapproved, status, newestFirst).Take(limit).ToList();
}
/// <summary>
/// Gets a paged list of topics
/// </summary>
/// <param name="index">Current page index</param>
/// <param name="limit">Page size limit</param>
/// <param name="unapproved">Include unapproved topics (optional)</param>
/// <param name="status">Array of status topic status types (optional)</param>
/// <param name="newestFirst">Sort by newest topics first (optional)</param>
/// <returns>Paged list of topics</returns>
public PagedList<Topic> TopicPageList(int index, int limit,
bool unapproved = false, TopicStatus[] status = null,
bool newestFirst = true)
{
var query = from p in db.Posts
select p;
return TopicQuery(query, unapproved, status, newestFirst).ToPagedList(index, limit);
}
/// <summary>
/// Gets a paged list of topics belonging to a tag(s)
/// (This uses the lowercase TagName property)
/// </summary>
/// <param name="tag">Array of tags to search</param>
/// <param name="index">Current page index</param>
/// <param name="limit">Page size limit</param>
/// <param name="unapproved">Include unapproved topics (optional)</param>
/// <param name="status">Array of status topic status types (optional)</param>
/// <param name="newestFirst">Sort by newest topics first (optional)</param>
/// <returns>Paged list of topics</returns>
public PagedList<Topic> TopicsByTag(string[] tag, int index, int limit,
bool unapproved = false, TopicStatus[] status = null, bool newestFirst = true)
{
var query = from t in db.PostTags
join pt in db.PostTagRelations on t.TagId equals pt.TagId
join p in db.Posts on pt.PostId equals p.PostId
where tag.Contains(t.TagName)
select p;
return TopicQuery(query, unapproved, status, newestFirst).ToPagedList(index, limit);
}
/// <summary>
/// Gets an individual topic belonging to a list of tag(s)
/// </summary>
/// <param name="tagId">Array of tag Ids to search</param>
/// <param name="index">Current page index</param>
/// <param name="limit">Page size limit</param>
/// <param name="unapproved">Include unapproved topics (optional)</param>
/// <param name="status">Array of status topic status types (optional)</param>
/// <param name="newestFirst">Sort by newest topics first (optional)</param>
/// <returns>Paged list of topics</returns>
public PagedList<Topic> TopicsByTagId(int[] tagId, int index, int limit,
bool unapproved = false, TopicStatus[] status = null, bool newestFirst = true)
{
var query = from t in db.PostTags
join pt in db.PostTagRelations on t.TagId equals pt.TagId
join p in db.Posts on pt.PostId equals p.PostId
where tagId.Contains(t.TagId)
select p;
return TopicQuery(query, unapproved, status, newestFirst).ToPagedList(index, limit);
}
/// <summary>
/// Gets a paged list of topics by the search criteria
/// </summary>
/// <param name="search">Title and body search terms</param>
/// <param name="index">Current page index</param>
/// <param name="limit">Page size limit</param>
/// <param name="unapproved">Include unapproved topics (optional)</param>
/// <param name="status">Array of status topic status types (optional)</param>
/// <param name="newestFirst">Sort by newest topics first (optional)</param>
/// <returns>Paged list of topics</returns>
public PagedList<Topic> TopicsBySearch(string search, int index, int limit,
bool unapproved = false, TopicStatus[] status = null, bool newestFirst = true)
{
var query = from p in db.Posts
where p.BodyText.Contains(search) || p.Title.Contains(search)
select p;
return TopicQuery(query, unapproved, status, newestFirst).ToPagedList(index, limit);
}
/// <summary>
/// Gets a paged list of replies by the search criteria
/// (only searches the bodytext)
/// </summary>
/// <param name="search">Search terms</param>
/// <param name="index">Current page index</param>
/// <param name="limit">Page size limit</param>
/// <param name="unapproved">Include unapproved topics (optional)</param>
/// <param name="status">Array of topic status types (optional)</param>
/// <param name="newestFirst">Sort by newest topics first (optional)</param>
/// <returns>Paged list of topics</returns>
public PagedList<Reply> RepliesBySearch(string search, int index, int limit,
bool unapproved = false, ReplyStatus[] status = null, bool newestFirst = true)
{
var query = from p in db.Posts
where p.BodyText.Contains(search)
select p;
return ReplyQuery(query, unapproved, status, newestFirst).ToPagedList(index, limit);
}
#endregion
#region Save methods
/// <summary>
/// Saves or creates a new reply under the given topic
/// </summary>
/// <param name="topic">Topic the reply belongs to</param>
/// <param name="reply">Reply to save</param>
public Reply SaveReply(Topic topic, Reply reply)
{
Post p = null;
DateTime dt = DateTime.UtcNow;
if (reply.Id != 0)
{
p = (from post in db.Posts
where post.PostId == reply.Id
select post).FirstOrDefault();
}
else
{
p = new Post();
p.CreatedDate = dt;
p.ReplyCount = 0;
p.ViewCount = 0;
db.Posts.InsertOnSubmit(p);
}
p.Approved = reply.Approved;
p.Status = (byte)reply.Status;
p.Threshold = reply.Threshold;
p.LastModified = dt;
p.BodyHtml = reply.Body;
p.BodyText = reply.Summary;
// Save reply
db.SubmitChanges();
// If this is a new reply...
if (p.PostId > 0 && reply.Id == 0)
{
// We now have an Id to set
reply.Id = p.PostId;
// Create Author, PostRelation and PostAuthor relationships
Author a = new Author();
a.MemberId = reply.CreatedBy.Id;
a.AuthorIP = reply.CreatedBy.IP;
a.AuthorName = reply.CreatedBy.Name;
a.AuthorEmail = reply.CreatedBy.Email;
a.AuthorWeb = reply.CreatedBy.Web;
db.Authors.InsertOnSubmit(a);
PostRelation pr = new PostRelation();
pr.ParentId = topic.Id;
pr.PostId = p.PostId;
db.PostRelations.InsertOnSubmit(pr);
db.SubmitChanges();
if (a.AuthorId > 0)
{
PostAuthor pa = new PostAuthor();
pa.AuthorId = reply.CreatedBy.Id;
pa.PostId = reply.Id;
db.PostAuthors.InsertOnSubmit(pa);
db.SubmitChanges();
}
}
return reply;
}
/// <summary>
/// Saves or creates a new topic
/// </summary>
/// <param name="topic">Topic to save</param>
/// <returns>Returns the saved topic</returns>
public Topic SaveTopic(Topic topic)
{
Post p = null;
DateTime dt = DateTime.UtcNow;
if (topic.Id != 0)
{
p = (from post in db.Posts
where post.PostId == topic.Id
select post).FirstOrDefault();
}
else
{
p = new Post();
p.CreatedDate = dt;
db.Posts.InsertOnSubmit(p);
}
p.Title = topic.Name;
p.Approved = topic.Approved;
p.Status = (byte)topic.Status;
p.Threshold = topic.Threshold;
p.LastModified = dt;
p.BodyHtml = topic.Body;
p.BodyText = topic.Summary;
p.ViewCount = topic.ViewCount;
p.ReplyCount = topic.ReplyCount;
// Save
db.SubmitChanges();
// If this is a new topic...
if (p.PostId > 0 && topic.Id == 0)
{
// Set the Id, now that we have one
topic.Id = p.PostId;
// Create author and set relationship
Author a = new Author();
a.MemberId = topic.CreatedBy.MemberId;
a.AuthorIP = topic.CreatedBy.IP;
a.AuthorName = topic.CreatedBy.Name;
a.AuthorEmail = topic.CreatedBy.Email;
a.AuthorWeb = topic.CreatedBy.Web;
db.Authors.InsertOnSubmit(a);
PostRelation pr = new PostRelation();
pr.ParentId = p.PostId; // Same since it's a topic
pr.PostId = p.PostId;
db.PostRelations.InsertOnSubmit(pr);
db.SubmitChanges();
if (a.AuthorId > 0)
{
PostAuthor pa = new PostAuthor();
pa.AuthorId = a.AuthorId;
pa.PostId = p.PostId;
db.PostAuthors.InsertOnSubmit(pa);
db.SubmitChanges();
}
}
topic.Slug = Util.GetSlug(p.Title, "topic");
ApplyTags(topic.Tags.ToList(), topic);
return topic;
}
#endregion
#region Tag methods
/// <summary>
/// Gets a list of tags by a search string
/// (usually for tag autocomplete)
/// </summary>
/// <param name="tag">Tag search string</param>
/// <param name="limit">Page size limit</param>
/// <returns></returns>
public List<Tag> TagsByName(string tag, int limit)
{
var query = from t in db.PostTags
orderby t.TagName ascending
where t.TagName.StartsWith(tag)
select new Tag
{
Id = t.TagId,
Name = t.TagName,
Slug = t.Slug,
DisplayName = t.TagName
};
if (limit > 0)
query = query.Take(limit);
return query.ToList();
}
/// <summary>
/// Associates a list of tags with the given topic
/// </summary>
/// <param name="tags">Tags to link to topic</param>
/// <param name="topic">Target topic</param>
private void ApplyTags(List<Tag> tags, Topic topic)
{
List<PostTagRelation> existing = (from pt in db.PostTagRelations
join t in db.PostTags on pt.TagId equals t.TagId
where pt.PostId == topic.Id
select pt).ToList();
// Clean existing relationships
db.PostTagRelations.DeleteAllOnSubmit(existing);
db.SubmitChanges();
// Setup the new relationships
List<PostTagRelation> newrelation = new List<PostTagRelation>();
// Store the new tags and get the complete list of tags
tags = StoreTags(tags, topic.CreatedBy);
foreach (Tag t in tags)
{
PostTagRelation tag = new PostTagRelation();
tag.TagId = t.Id;
tag.PostId = topic.Id;
newrelation.Add(tag);
}
// Save the new tag relationships
db.PostTagRelations.InsertAllOnSubmit(newrelation);
db.SubmitChanges();
}
/// <summary>
/// Finds existing tags and creates new tags with the associated creator if
/// the tag doesn't exist
/// </summary>
/// <param name="tags">List of tags to create/find</param>
/// <param name="creator">Tag creator</param>
/// <returns>List of found and newly created tags</returns>
private List<Tag> StoreTags(List<Tag> tags, Creator creator)
{
// Complete list of all tags
List<Tag> complete = new List<Tag>();
// Created date
DateTime dt = DateTime.UtcNow;
string[] search = tags.Select(tg => tg.Name).ToArray();
string[] existing = (from t in db.PostTags
where search.Contains(t.TagName)
select t.TagName).ToArray();
// Tags except those already in the database
string[] newtags = search.Except(existing).ToArray();
// We have new tags to save
if (newtags.Length > 0)
{
List<PostTag> savetags = (from tg in tags
where newtags.Contains(tg.Name)
select new PostTag
{
DisplayName = tg.DisplayName,
TagName = tg.DisplayName.ToLower(),
Slug = Util.GetSlug(tg.DisplayName, tg.Name),
Status = (byte)TagStatus.Open,
LastModified = dt,
CreatedDate = dt,
BodyHtml = "",
BodyText = ""
}).ToList();
if (savetags.Count() > 0)
{
db.PostTags.InsertAllOnSubmit(savetags);
db.SubmitChanges();
// Create author info for each new tag
Author author = getAuthor(creator);
List<TagAuthor> authors = (from tg in savetags
select new TagAuthor
{
AuthorId = author.AuthorId,
TagId = tg.TagId
}).ToList();
db.TagAuthors.InsertAllOnSubmit(authors);
db.SubmitChanges();
}
// Get all existing and newly inserted tags
complete = (from tg in db.PostTags
where search.Contains(tg.TagName)
select new Tag
{
Id = tg.TagId,
Name = tg.TagName,
DisplayName = tg.DisplayName,
Slug = tg.Slug
}).ToList();
}
return complete;
}
#endregion
#region Queries
/// <summary>
/// Creates a deferred execution IQueryable to search topics
/// </summary>
/// <param name="posts">Initial search query</param>
/// <returns>Topic IQueryable</returns>
private IQueryable<Topic> TopicQuery(IQueryable<Post> posts,
bool unapproved = false, TopicStatus[] status = null,
bool newestFirst = true)
{
var query = from p in posts
join au in db.PostAuthors on p.PostId equals au.PostId
join a in db.Authors on au.AuthorId equals a.AuthorId
join m in db.Members on au.AuthorId equals m.MemberId into author
from auth in author.DefaultIfEmpty() // Empty if anonymous post
let postauthor = getCreator(a, auth)
let tags = GetTagsForTopic(p.PostId)
select new { p, postauthor, tags };
// Include unapproved topics?
query = (unapproved) ?
query.Where(r => r.p.Approved == false) :
query.Where(r => r.p.Approved == true);
// Any status other than "Open"?
query = (status != null) ?
query = query.Where(r => status.Contains((TopicStatus)r.p.Status)) :
query = query.Where(r => r.p.Status == (byte)TopicStatus.Open);
// Sort by new topics first?
query = (newestFirst) ?
query.OrderByDescending(r => r.p.CreatedDate) :
query.OrderBy(r => r.p.CreatedDate);
return from r in query
select new Topic
{
Id = r.p.PostId,
Name = r.p.Title,
CreatedBy = r.postauthor,
CreatedDate = r.p.CreatedDate,
LastModified = r.p.LastModified,
Summary = r.p.BodyText,
Slug = Util.GetSlug(r.p.Title, "topic", 50, true),
Tags = new LazyList<Tag>(r.tags),
ViewCount = r.p.ViewCount,
ReplyCount = r.p.ReplyCount,
Threshold = (float)r.p.Threshold,
Status = (TopicStatus)r.p.Status
};
}
/// <summary>
/// Creates a deferred execution IQueryable to search replies
/// </summary>
/// <param name="posts">Initial posts query</param>
/// <param name="status">Status restriction array</param>
/// <param name="newestFirst">Sort by new replies first</param>
/// <returns>Reply IQueryable</returns>
private IQueryable<Reply> ReplyQuery(IQueryable<Post> posts, bool unapproved, ReplyStatus[] status, bool newestFirst)
{
var query = from p in posts
join au in db.PostAuthors on p.PostId equals au.PostId
join a in db.Authors on au.AuthorId equals a.AuthorId
join m in db.Members on au.AuthorId equals m.MemberId into author
from auth in author.DefaultIfEmpty() // Empty if anonymous post
let postauthor = getCreator(a, auth)
select new { p, postauthor };
// Include unapproved replies?
query = (unapproved) ?
query.Where(r => r.p.Approved == false) :
query.Where(r => r.p.Approved == true);
// Any status other than "Open"?
query = (status != null) ?
query = query.Where(r => status.Contains((ReplyStatus)r.p.Status)) :
query = query.Where(r => r.p.Status == (byte)ReplyStatus.Open);
// Sort by new replies first?
query = (newestFirst) ?
query.OrderByDescending(r => r.p.CreatedDate) :
query.OrderBy(r => r.p.CreatedDate);
return from r in query.AsQueryable()
select new Reply
{
Id = r.p.PostId,
CreatedBy = r.postauthor,
CreatedDate = r.p.CreatedDate,
LastModified = r.p.LastModified,
Body = r.p.BodyHtml,
Threshold = (float)r.p.Threshold
};
}
/// <summary>
/// Helper finds the tags for a topic by id
/// </summary>
/// <param name="id">Topic id to search</param>
/// <returns>IQueryable Tag</returns>
private IQueryable<Tag> GetTagsForTopic(int id)
{
return from t in db.PostTags
join pt in db.PostTagRelations on t.TagId equals pt.TagId
where pt.PostId == id
select new Tag
{
Id = t.TagId,
Name = t.TagName,
Slug = t.Slug,
DisplayName = t.DisplayName,
CreatedDate = t.CreatedDate,
LastModified = t.LastModified
};
}
#endregion
#region Author/Creator Helpers
/// <summary>
/// Helper function generates a save friendly Author from a given Creator
/// </summary>
/// <param name="c">Creator data</param>
/// <returns>Author object</returns>
private static Author getAuthor(Creator c)
{
if (c == null)
return null;
Author author = new Author();
if (c.Id > 0)
{
author.AuthorId = c.Id;
}
else
{
author.AuthorEmail = c.Email;
author.AuthorWeb = c.Web;
}
author.AuthorIP = c.IP;
author.AuthorName = c.Name;
return author;
}
/// <summary>
/// Finds or creates a Creator object from given author information
/// </summary>
/// <param name="a">Saved author information</param>
/// <param name="m">Optional membership information</param>
/// <returns>Composite Creator object</returns>
private static Creator getCreator(Author a, Member m)
{
Creator au = new Creator();
au.IP = a.AuthorIP;
if (m != null)
{
au = getCreator(m);
}
else
{
au.LastModified = DateTime.MinValue;
au.Id = a.AuthorId;
au.Name = a.AuthorName;
au.DisplayName = a.AuthorName;
au.Email = a.AuthorEmail;
au.Web = a.AuthorWeb;
}
return au;
}
/// <summary>
/// Helper function generates a Creator object from membership info
/// </summary>
/// <param name="m">Member object</param>
/// <returns>Composite Creator object</returns>
private static Creator getCreator(Member m)
{
return new Creator
{
Id = m.MemberId,
Name = m.Username,
DisplayName = m.DisplayName,
Email = m.Email,
Web = m.Web,
Slug = Util.GetSlug(m.Username, m.MemberId.ToString(), 70),
CreatedDate = m.CreatedDate,
LastModified = m.LastActivity,
Avatar = m.Avatar
};
}
#endregion
}
}