Introduction
The idea it's to make a simple social network to post text, pictures and generic files from pc and mobile about a generic topic, without registration and app to be downloaded.
The solution flows naturally: email, the most implemented comunication system of any device, including my old, not smart, telephone.
So the system will build in this way: an smtp server linked to a web interface, whenever a mail is recieved it will be posted on the corresponding group.
Example: if we send an email to genova@supb.eu it will be shown with others in supb.eu/genova.
Background
This project run upon NetFluid Application Server, the free version is downloadable here
Using the code
Step 1: Setting up the project
First of we need to create a new NetFluid Web App, a Visual Studio template is available at link above.
Our web app is compilable as portable executable (*.exe) with embedded web interface and server or as library (*.dll) to be loaded by another instance of NetFluid.
In this case we opt for executable so we need to set up the AppConfig to set basics NetFluid settting:
<NetFluidSettings MaxPostSize="268435456" SessionDuration="7200">
<Interfaces>
<Interface IP="127.0.0.1" Port="8080" />
</Interfaces>
<PublicFolders>
<PublicFolder RealPath="./Public" UriPath="/" />
</PublicFolders>
</NetFluidSettings>
And to load the NetFluid Engine
static void Main(string[] args)
{
Engine.Load(Assembly.GetExecutingAssembly());
Engine.Start();
}
Step 2: Add the email system
After the experience with the huge-mangrovia code of LumiSoft of the previous article I opted for an in-house solution 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();
}
}
}
The smtp server can be also embedded in any web page (FluidPage) of our project without being garbage collected, just mark it as static.
Step 3: Data model
We got just two entities: the users and the messages.
[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;
}
The BsonId attribute and the ObjectId type are present because we use MongoDB if you don't intend to install it you can simply use the PersistentBag collection in NetFluid.Collection.
Step 4: The posting system
Now got all that is need, the web and smtp interface and the data model, it's time to program the core of our project: the posting system.
If there isn't a user for the sender we create a new one and after that we simply copy the message in each group.
The group of a message it's specified simply the the Group
field in Message.
smpt.MessageReceived +=(s,e) =>
{
var body = e.Message.Body;
User user;
try
{
#region GET USER OR CREATE IT
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
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);
}
};
Step 5: Read the posts
Now we got our database with all the messages and relatives users, it's time to show them.
To achieve this we need to define a web page of our web app, the base type for this is FluidPage.
NOTE: NetFluid use a huge amount of run-time generated code so FluidPages defined in the code must be signed as PUBLIC
namespace SupB
{
public class MessageManager : FluidPage
{
[Route("/",true)]
public void Index(string group,string postId)
{
}
}
}
With the Route Attribute we tell to the NetFluid engine to call this page on the main uri.
With the parametrized
flag set on true, we tell to engine to take arguments the of method from the uri, instead from post or get parameters.
Now we got four possibilities:
http://supb.eu/ | The main page of our site | group =null and postId =null |
http://supb.eu/style.css | And other public files | group ="style.css" and postId =null |
http://supb.eu/group | The user requested a group | group ="group" and postId =null |
http://supb.eu/group/message | The user requested a single post | group ="group" and postId ="message" |
The first and the fourth case are obvious, we can distinguish the second and the third case with the IsPublicFile function, or resolve the problem at his root moving contents (images, styles, javascript, etc.) on another domain.
if (group==null)
{
Render("SupB.UI.Master");
return;
}
if (IsPublicFile("/"+group))
{
Blocking = false;
return;
}
group = group.ToLowerInvariant();
var messages = Database.Group(@group);
if (!messages.Any())
{
Render("SupB.UI.Error", new { Message = "Empty group" });
return;
}
#region SHOW A SINGLE POST IF REQUESTED
if (postId!=null)
{
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});
Step 6: assemble the bodywork
Has we seen above, after taking the messages posted we pass them to a function called Render that display an *.html page to the output, passing an object translated into a dictionary.
NOTE: HTML page are runtime compiled by the NetFluid engine, so to use them remember to mark them as EMBEDDED RESOURCES
In this project we got four pages:
Master: The main page and index of the web app, others page will inherit this one
Error: To show error messages
Post: To show a single post
Group: To show post inside a group
Here we can see the structure of Master, with the two NetFluid instruction define with these we can define two field of the page overridable by derived types.
In this case we defined two fields Column and Center
Any other C# function can be inserted into the html via the % symbol for single line instruction and <% %> for multiline instruction.
<!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="http://www.codeproject.com/bootstrap-responsive.css" rel="stylesheet" />
<link href="http://www.codeproject.com/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="http://www.codeproject.com/">
<img src="http://www.codeproject.com/logo.png" alt="Sup /b!"/>
</a>
</h1>
%define Center
<div>
<img src="http://www.codeproject.com/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>
Now, with the NetFluid specific template instruction, we can tell to Group page to inherit the Master page and redefine the two fields.
The {% %} is used to print a value inside the 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="http://www.codeproject.com/{% 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()>
NOTE: Single line cycle and condition are closed by end instead of }
Multiline:
%
for(int i=0;i < count; i++)
{
}
%>
Single line:
% for(int i=0;i < count; i++)
%
% end for
Points of Interest
There isn't any good free .net smtp component in the whole internet.
See the result on http://supb.eu/
History
First version