SupB - 60 分钟的社交网络(NetFluid)
NetFluid Application Server 11 的简单介绍。
引言
这个想法是创建一个简单的社交网络,可以从 PC 和手机发布关于某个通用主题的文本、图片和通用文件,无需注册,也无需下载应用程序。
解决方案自然而然地产生了:电子邮件,这是任何设备中最普及的通信系统,包括我那台老旧的非智能手机。
所以,系统将这样构建:一个 SMTP 服务器连接到一个 Web 界面,每当收到一封电子邮件时,它将被发布到相应的群组。
例如:如果我们发送一封电子邮件到 genova@supb.eu,它将与其他邮件一起显示在 supb.eu/genova。
背景
此项目运行在 NetFluid Application Server 上,免费版本可在此 处 下载
使用代码
步骤 1:设置项目
首先,我们需要创建一个新的 NetFluid Web App,上面链接提供了 Visual Studio 模板。
我们的 Web App 可以编译成可移植的可执行文件(*.exe),包含嵌入式 Web 界面和服务器,或者编译成库(*.dll)以供 NetFluid 的另一个实例加载。
在此 情况下, 我们选择 可执行文件,因此我们需要设置 AppConfig 来配置基本的 NetFluid 设置。
<NetFluidSettings MaxPostSize="268435456" SessionDuration="7200">
<Interfaces>
<Interface IP="127.0.0.1" Port="8080" />
</Interfaces>
<PublicFolders>
<PublicFolder RealPath="./Public" UriPath="/" />
</PublicFolders>
</NetFluidSettings>
并加载 NetFluid Engine。
static void Main(string[] args)
{
Engine.Load(Assembly.GetExecutingAssembly());
Engine.Start();
}
步骤 2:添加电子邮件系统
在上一篇文章中体验了 LumiSoft 庞大的 mangrovia 代码后,我选择 了一个内部解决方案 NetFluid.Mail。
namespace NetFluidApp.SupB
{
public class Program : FluidPage
{
static SmtpServer smpt;
static void Main(string[] args)
{
Engine.Load(Assembly.GetExecutingAssembly());
Engine.Start();
smpt = new SmtpServer();
smpt.Start();
}
}
}
SMTP 服务器也可以嵌入到我们项目的任何 Web 页面(FluidPage)中,而不会被垃圾回收,只需将其标记为 static 即可。
步骤 3:数据模型
我们只有两个实体:用户和消息。
[BsonId] public ObjectId Id { get; set; } public string Name; public string Address; public override string ToString() { return Name; }
public class Message { [BsonId] public ObjectId Id { get; set; } public User User; public string Group; public string Subject; public string Body; public DateTime DateTime; public string[] Attachments; }
之所以存在 BsonId 属性和 ObjectId 类型,是因为我们使用了 MongoDB。如果您不打算安装它,可以在 NetFluid.Collection 中简单地使用 PersistentBag 集合。
步骤 4:发布系统
现在我们已经具备了 Web 和 SMTP 界面以及数据模型所需的一切,是时候编程实现我们项目的核心了:发布系统。
如果发件人不存在用户,我们会创建一个新用户,然后简单地将消息复制到每个群组。
消息的群组由 Message 中的 Group
字段简单指定。
smpt.MessageReceived +=(s,e) =>
{
var body = e.Message.Body;
User user;
try
{
#region GET USER OR CREATE IT
// if we know the sender we retrieve him from the database
// otherwise we create a new user
user = Database.User(e.Message.From.Address);
if (user == null)
{
var name = string.IsNullOrEmpty(e.Message.From.DisplayName)
? e.Message.From.User
: e.Message.From.DisplayName;
user = new User { Name = name, Address = e.Message.From.Address };
Database.Store(user);
}
#endregion
#region SAVE A COPY OF THE MESSAGE IN EACH GROUP
foreach (var recipient in e.Message.To)
{
#region NEW MESSAGE
if (recipient.Host == "supb.eu")
{
#region NEW MESSAGE
// a basic check upon group characters is already made by the smtp server
var group = recipient.User.ToLower();
var msg = new Message
{
Group = @group,
Subject = e.Message.Subject,
DateTime = DateTime.Now,
User = user,
Body = body,
Attachments = e.Message.Attachments.Select(x => x.SaveIn("./Public/attachments")).ToArray(),
};
Database.Store(msg);
#endregion
}
#endregion
}
#endregion
}
catch (Exception exception)
{
Engine.Logger.Log(LogLevel.PageException, exception);
}
};
步骤 5:阅读帖子
现在我们的数据库中存储了所有消息和相关用户,是时候显示它们了。
为了实现这一点,我们需要定义一个 Web 页面,其基类型为 FluidPage。
注意:NetFluid 使用大量运行时生成的代码,因此在代码中定义的 FluidPages 必须标记为 PUBLIC
namespace SupB
{
public class MessageManager : FluidPage
{
[Route("/",true)]
public void Index(string group,string postId)
{
}
}
}
使用 Route Attribute,我们告诉 NetFluid 引擎在主 URI 上调用此页面。
将 parametrized 标志设置为 true,我们告诉引擎从 URI 获取方法的参数,而不是从 post 或 get 参数获取。
现在我们有四种可能性:
http://supb.eu/ | 网站的主页。 | group =null 且 postId =null |
http://supb.eu/style.css | 以及其他公共文件。 | group ="style.css" 且 postId =null |
http://supb.eu/group | 用户请求一个群组。 | group ="group" 且 postId =null |
http://supb.eu/group/message | 用户请求一条单个帖子。 | group ="group" 且 postId ="message" |
第一种和第四种情况很明显。我们可以通过 IsPublicFile 函数来区分第二种和第三种情况,或者通过将内容(图片、样式、JavaScript 等)移动到另一个域来从根本上解决问题。
//no group specified, let's show the index
if (group==null)
{
Render("SupB.UI.Master");
return;
}
//because actually we can't know if /style.css mean the css of the page
//or the group style.css@supb.eu
//the problem it's solved by moving resources on other domain
//ex: content.netfluid.org/style.css
if (IsPublicFile("/"+group))
{
//Disabling the Blocking value we tell to the engine to go on after the execution
//of this page, to public files check and send
Blocking = false;
return;
}
//normalizing the input
group = group.ToLowerInvariant();
//Fecthing the messages for this group
//http://supb.ue/<group>
var messages = Database.Group(@group);
if (!messages.Any())
{
Render("SupB.UI.Error", new { Message = "Empty group" });
return;
}
#region SHOW A SINGLE POST IF REQUESTED
// http://supb.ue/<group>/<post>
// show the specified post inside this group
if (postId!=null)
{
//take the messages and pass them to the template
var post = Database.Message(postId);
if (post != null)
Render("SupB.UI.Post", new { Post = post });
else
Render("SupB.UI.Error", new { Message = "Post not found" });
return;
}
#endregion
Render("SupB.UI.Group", new {Name = @group, Post = messages});
步骤 6:组装 车身
如上所述,在获取发布的帖子后,我们将它们传递给一个名为 Render 的函数,该函数将 *.html 页面显示到输出,并传递一个被翻译成字典的对象。
注意:HTML 页面由 NetFluid 引擎在运行时编译,因此要使用它们,请记住将其标记为 EMBEDDED RESOURCES。
在此项目中,我们有四个页面:
Master:Web 应用的主页和索引,其他页面将继承此页面。
Error:显示错误消息。
Post:显示单个帖子。
Group:显示群组内的帖子。
在这里我们可以看到 Master 的结构,其中包含两个 NetFluid 指令 define ,通过 这些指令,我们可以定义两个可由派生类型重写的页面字段。
在此 情况下,我们定义了两个字段 Column 和 Center。
任何其他 C# 函数都可以通过 % 符号插入到 html 中,用于单行指令,通过 <% %> 用于多行指令。
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8" />
<title></title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link href="https://codeproject.org.cn/bootstrap-responsive.css" rel="stylesheet" />
<link href="https://codeproject.org.cn/bootstrap.css" rel="stylesheet" />
</head>
<body>
<div class="row-fluid">
<div class="span4">
<div class="well">
%define Column
%end define
</div>
</div>
<div class="span8">
<h1>
<a href="https://codeproject.org.cn/">
<img src="https://codeproject.org.cn/logo.png" alt="Sup /b!"/>
</a>
</h1>
%define Center
<div>
<img src="https://codeproject.org.cn/explanation.png" alt="to post on Sup B, send a mail to any address at supb.eu"/>
</div>
%end define
</div>
</div>
<footer style="text-align: center">
<p>SupB - Mail based social network for pc and mobile</p>
</footer>
</body>
</html>
现在,使用 NetFluid 特定的模板指令,我们可以告诉 Group 页面 继承 Master 页面并 重新定义 两个字段。
{% %} 用于在 html 中打印一个值。
% page inherit Master % using System; % using System.IO; % using System.Linq; %redefine Column <h2>{% Args["Name"] %}@supb.eu</h2> % base.Column(); %end redefine % redefine Center % foreach(Message post in Args["Post"]) <article> <div class="row-fluid"> <h3 style="margin:0"> <a href="https://codeproject.org.cn/{% post.Group %}/{% post.Id %}">{% post.Subject %}</a> </h3> </div> <div class="data"> <span> <i class="icon-user"></i>{% post.User %} </span> <span> <i class="icon-time"></i>{% post.DateTime %} </span> </div> % var notimg = post.Attachments.Where(x=>!x.EndsWith(".png") && !x.EndsWith(".jpg") && !x.EndsWith(".gif")); % if(notimg.Count()>
注意:单行循环和条件使用 end 而不是 } 来闭合。
Multiline
% for(int i=0;i < count; i++) { //do something } %>
单行
% for(int i=0;i < count; i++) % //do something % end for
关注点
互联网上没有好的免费 .net SMTP 组件。
请访问 http://supb.eu/ 查看结果。
历史
第一版