给BlogEngine增加日志点击数

2008-12-23

有朋友在留言时提到,所以这里放出来。
我用的是sqlserver,如果是xml就得改相应的代码了。
先在数据表中增加一字段:hit,给post.cs中增加一Hit字段。
同时更改provider的代码,这里就不给出来了。


blogservice.cs添加UpdateHit(guid id)方法
public static void UpdateHit(guid id)
{
LoadProviders();
_provider.UpdateHit(id);
}

sqldataprovider.cs
public override void UpdateHit(guid id)
{
string sqlQuery = " update dbo.be_Posts set hit = hit+1 where postid=@id";
SqlHelper.ExecuteNonQuery(SqlHelper.ConnectionString, CommandType.Text, sqlQuery, new SqlParameter("@id", id));
}

post.cs
public static void UpdateHit(guid id)
{
Posts.Find(delegate(Post p) { return p.Id== id; }).Hit++; //更新内存数据
BlogService.UpdateHit(id); //更新数据库记录
}

post.aspx.cs

if (Request.QueryString["id"] != null && Request.QueryString["id"].Length == 36)
{
Guid id = new Guid(Request.QueryString["id"]);
this.Post = Post.GetPost(id);

if (Post != null)
{
Post.UpdateHit(this.Post.Id); //增加
......................

更改完毕。

[BlogEngine扩展]IP2Area

2008-12-05

该插件将评论者的IP转为地区,数据取自于http://www.ip138.com
在这里表示感谢!
该扩展要在CommentAdded事件上进行处理,首先添加一个字段UserArea用来保存地区。
放心,这里的是异步处理的,不会造成页面的明显延迟。

代码如下:

/// <summary>
///Ip2Area 的摘要说明
/// </summary>
///
[Extension("IP转为地区", "1.0", "JasonYi")]
public class Ip2Area
{
public Ip2Area()
{
Post.CommentAdded += new EventHandler<EventArgs>(Post_CommentAdded);
}

void Post_CommentAdded(object sender, EventArgs e)
{
Comment p = sender as Comment;
if(p!=null)
new HttpAsyncGetIPArea(p,"http://www.ip138.com/ips.asp").Execute();
}
}
public class HttpAsyncGetIPArea
{
private Comment _comment;
private string _url;
public HttpAsyncGetIPArea(Comment c,string url)
{
_comment = c;
_url = url;
}
public Comment comment
{
get { return _comment; }
}
private HttpWebRequest Request;

public void Execute()
{
Request = (HttpWebRequest)WebRequest.Create(_url);
Request.Method = "POST";
Request.ContentType = "application/x-www-form-urlencoded";
Byte[] data = System.Text.Encoding.ASCII.GetBytes("action=2&ip=" + _comment.IP);
Request.ContentLength = data.Length;
using (Stream stream = Request.GetRequestStream())
{
stream.Write(data, 0, data.Length);
}
Request.BeginGetResponse(ProcessRespose, this);
}
private void ProcessRespose(IAsyncResult async)
{
HttpAsyncGetIPArea item = (HttpAsyncGetIPArea)async.AsyncState;
try
{
using (HttpWebResponse response = (HttpWebResponse)item.Request.EndGetResponse(async))
{
string RStr = string.Empty;

using (StreamReader sr = new StreamReader(response.GetResponseStream(), Encoding.Default))
{

RStr = sr.ReadToEnd();
}
Regex reg = new Regex(@"本站主数据:([^<li>]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
MatchCollection mc = reg.Matches(RStr);
string area = string.Empty;
if (mc.Count > 0)
{
area = mc[0].Value.Replace("本站主数据:", string.Empty);
}
if (!string.IsNullOrEmpty(area))
{
string sql = "UPDATE be_PostComment SET UserArea=@Area WHERE PostCommentID=@Id";
SqlParameter[] para = { new SqlParameter("@Area", area), new SqlParameter("@Id", comment.Id) };
SqlHelper.ExecuteNonQuery(SqlHelper.ConnectionString, System.Data.CommandType.Text, sql, para);
comment.UserArea = area;
}
}
}
catch (Exception ex)
{
//throw new System.Net.WebException("Get HttpResponse Error");
}
finally
{
Request.Abort();
}
}
}

微软推出For .Net 3.5 Chart控件

2008-10-27

的确是个好消息!弥补了.Net在生成图表方面的不足,
该控件支持winform与webform,不过按官方所言,该控件目前只有.Net formwork 3.5版。

控件下载:
下载地址
中文语言包下载:
下载地址

Demo下载
http://code.msdn.microsoft.com/mschart/Release/ProjectReleases.aspx?ReleaseId=1591









赶快试用一下吧

[BlogEngine插件]对日志进行加密

2008-10-25

这里的对日志加密是指输入正确密码以后才能正常访问。
示例:
http://yibin.us/Archives/6035.aspx
密码:123456
针对于BlogEngine来说,可以用它丰富的插件机制来实现。

首先明确,完成这个插件需要做哪些工作:
1、数据表结构的变化。要增加密码项,“密码”字段当然是少不了了。
2、表结构的变化势必会引起Post实体类属性的变化。
3、Post.Serving时触发事件,该事件负责针对性的改变e.Body
4、认证的实现。
这里我用的是SqlServer数据库,打开be_Posts表,执行以下语句

ALTER TABLE dbo.be_Posts
ADD AccessPassword VARCHAR(100) NOT NULL DEFAULT ''
GO

对应地在BlogEngine.Core.Post中增加属性AccessPassword

private string _AccessPassword = string.Empty;
public string AccessPassword
{
get { return _AccessPassword; }
set {
if (_AccessPassword != value) MarkChanged(&quot;AccessPassword&quot;);
_AccessPassword = value;
}
}

这里的AccessPassword不为空表明该日志需要密码才能访问。当然,这里还得修改相应的数据访问层代码,来初始化AccessPassword字段。这里略去。
下一步编写插件,针对Post.Serving事件编写,全部代码:

[Extension(&quot;加密日志&quot;,&quot;1.0&quot;,&quot;yibin&quot;)]
public class ZPostAccessExtension : System.Web.SessionState.IRequiresSessionState
{
string cookiekey = string.Empty;
public ZPostAccessExtension()
{
Post.Serving += new EventHandler&lt;ServingEventArgs&gt;(Post_Serving);
}

void Post_Serving(object sender, ServingEventArgs e)
{
Post p = sender as Post;
if (HttpContext.Current.User.IsInRole(&quot;administrators&quot;) || HttpContext.Current.User.Identity.Name == p.Author)
return;
cookiekey = &quot;POSTACCESS_&quot; + p.Id;
//这里很关键,用Session来判断当前用户没有权限访问,如果指定的session存在,则直接退出,否则更改e.Body值。
if (HttpContext.Current.Session[cookiekey] != null)
{
return;
}
if (!string.IsNullOrEmpty(p.AccessPassword))
{
if (e.Location == ServingLocation.PostList)
{
e.Body = &quot;加密日志,您无权查看&quot;;
}
else
{
e.Body = string.Format(@&quot;&lt;p class=&quot;&quot;AccessPassword&quot;&quot;&gt;加密日志,请输入密码查阅&lt;/p&gt;&lt;input type=&quot;&quot;password&quot;&quot; name=&quot;&quot;AccessPassword_{0}&quot;&quot; id=&quot;&quot;AccessPassword_{0}&quot;&quot; class=&quot;&quot;AccessPassword&quot;&quot; /&gt;&lt;input type=&quot;&quot;button&quot;&quot; value=&quot;&quot;确定&quot;&quot; title=&quot;&quot;确定&quot;&quot; onclick=&quot;&quot;Check();&quot;&quot;/&gt;&quot;, p.PostIdentity);
e.Body = e.Body.Replace(&quot;&lt;&quot;, &quot;&lt;&quot;).Replace(&quot;&gt;&quot;, &quot;&gt;&quot;);  //这里要对已经HtmlEncode后的字符进行还原,否则显示不正确。
}
}
}
}

接下来就是实现认证这一步,我采用的是实现ICallbackEventHandler接口来进行无刷新操作。
先看很简单的js代码

&lt;script type=&quot;text/javascript&quot;&gt;
var accessMessage = document.getElementById('AccessMessage');
function Check()
{
accessMessage.style.display='block';
var pwd = '';
var panel = document.getElementById('AccessPassword_&lt;%=Post.PostIdentity %&gt;');  //DOM找到密码输入框
if(panel) {
pwd = panel.value;
}
if(pwd==''){
accessMessage.innerHTML='密码不能为空';
panel.focus();
return false;
}
accessMessage.innerHTML='正在验证......';
&lt;%= Page.ClientScript.GetCallbackEventReference(this,&quot;pwd&quot;,&quot;checkCallback&quot;,&quot;&quot;)%&gt;;
}

function checkCallback(rValue) {
if(rValue=='error')
{
accessMessage.style.display='block';
accessMessage.innerHTML='密码认证失败';
setTimeout(function(){accessMessage.style.display='none';},2000);
}
else{
accessMessage.style.display='block';
accessMessage.innerHTML='验证成功';
setTimeout(function(){document.getElementById('entrybody_&lt;%=Post.PostIdentity %&gt;').innerHTML = rValue;},500);
setTimeout(function(){accessMessage.style.display='none';},3000);
}
}
function ServerError(error) {
alert(error);
}
&lt;/script&gt;

.cs文件中的关于ICallbackEventHandler接口的实现

#region ICallbackEventHandler 成员
protected string _callback = string.Empty;
string ICallbackEventHandler.GetCallbackResult()
{
return _callback;
}

void ICallbackEventHandler.RaiseCallbackEvent(string eventArgument)
{
if (Post.AccessPassword == eventArgument.Trim())
{
string SessionKey = &quot;POSTACCESS_&quot; + Post.Id;
HttpContext.Current.Session.Add(SessionKey, true);  //注意这里,当密码验证成功后写session
string path = Utils.RelativeWebRoot + &quot;themes/&quot; + BlogSettings.Instance.Theme + &quot;/PostView.ascx&quot;;

PostViewBase postView = (PostViewBase)LoadControl(path);
postView.Post = Post;
postView.Location = ServingLocation.SinglePost;
_callback = postView.Body;   //此处仍然会执行Post.Serving 事件,但因为前面已经写入了Session,所以触发Post.Serving 事件时将直接返回日志内容。
//因为我想每次访问日志都需要输密码,所以,当正确返回日志内容后,清除掉该Session
HttpContext.Current.Session.Remove(SessionKey);
}
else
{
_callback = &quot;error&quot;;
}
}

#endregion

OK,整个过程就是如此,很是简单吧。
同样的原理,可以实现类似于论坛的“回复后可见”同样的功能,如果想长时间维持日志的正常查看权,可将Session换成Cookie。
另外,BlogEngine默认在web.config中将enableSessionState设为了false,请一定要设为true,否则无法使用Session!
我就是没有认真看web.config,在Session的获取上大费周折。

密码保护:测试加密日志

2008-10-25

这是一篇受密码保护的文章。您需要提供访问密码:


ImageCropper For .Net

2008-10-20

这是一个基于.Net的图片裁剪控件,使用比较广泛,如Gravatar中的头像上传与裁剪功能的实现。
当然Gravatar并不是用的这个控件。
今天在codeproject中无意发现的,觉得不错,拿来与大家分享一下。


裁剪后

基本参数:
MaintainAspectRatio,bool型,是否固定宽高比例进行裁剪。
CroppedImageHeight,int型,裁剪后的图片高
CroppedImageWidth,int型,裁剪后的图片宽
JpegQuality,int型,裁剪后的图片质量
裁剪时直接调用ImageCropper的Crop()方法即可。

下载地址:
http://www.uushare.com/user/yibin/file/899549

让.Net验证控件与自定义验证合作无间

2008-10-16

场景:
一个注册表单,其间有.Net自带的验证控件,但有一些验证必须得借助于另外的一些js,如验证是否复选了注册协议(这里为了说明,只举这样一个简单的例子)。
这样的话,会涉及到二次验证,第一次可能是.Net验证控件的验证,第二次会认证用户有没有复选注册协议,如何让这二者合作无间呢?
这里不得不提到.Net的Page_ClientValidate()函数,该函数返回当前Page页中的表单有没有通过Validate的验证.

新建一个Page页面,简单的放一些控件

<asp:TextBox ID="TextBox1" runat="server"></asp:TextBox>
<asp:Button ID="Button1" runat="server" Text="Button"
ValidationGroup="submit" CausesValidation="True"
onclick="Button1_Click" />
<input type="checkbox" id="checkbox" />我已阅读注册协议
<asp:RequiredFieldValidator ID="RequiredFieldValidator1" runat="server"
ControlToValidate="TextBox1" Display="Dynamic" ErrorMessage="不能为空"
ValidationGroup="submit"></asp:RequiredFieldValidator>

页面呈现

TextBox留空,点击Button,会进行一次验证,当在TextBox中输入文本时,再点击提交,验证通过,引发OnClick事件,但用户是否复选了注册协议此处还未做判断。

此时需要我们手写验证函数了

<script type="text/javascript">
function test() {
var isCheck = document.getElementById('checkbox').checked;
if (isCheck) {
//Page_ClientValidate('submit'),这里有不同的ValidGroup,所以Page_ClientValidate传递是指定的GroupName
if (Page_ClientValidate('submit')) {
alert('验证成功');
return true;
}
}
else {
alert('您必须同意注册协议');
return false;
}

return false;
}
</script>

修改后的代码

<asp:TextBox ID="TextBox1" runat="server"></asp:TextBox>
<asp:Button ID="Button1" runat="server" Text="Button" ValidationGroup="submit" CausesValidation="True"
OnClientClick="return test();" OnClick="Button1_Click" />
<input type="checkbox" id="checkbox" />我已阅读注册协议
<asp:RequiredFieldValidator ID="RequiredFieldValidator1" runat="server" ControlToValidate="TextBox1"
Display="Dynamic" ErrorMessage="不能为空" ValidationGroup="submit"></asp:RequiredFieldValidator>

预览页面



到这里好像已经结束了,但查看html源代码会发现:

<input type="submit" name="Button1" value="Button"
onclick="return test();WebForm_DoPostBackWithOptions(new WebForm_PostBackOptions("Button1", "", true, "submit", "", false, false))" id="Button1" />

Button1除了有return test()外还有WebForm_DoPostBackWithOptions这样一段,很明显WebForm_DoPostBackWithOptions是不会被执行的,去掉这段也很简单,
将Button1的CausesValidation设为false即可。

当然,你也可以完全不用验证控件来实现。

用BeginGetResponse进行异步处理

2008-09-26

在今天做的小项目中
用户登录成功后要访问另一个应用的一个url,只是get过去。
首先想到用HttpWebRequest去直接GetResponse()就OK了
但这里会有一个问题:如果另一个应用的响应时间过慢或网络响应过慢,直接会导致登录时会卡一下。
可以完全使用HttpWebRequest的异步方法获取响应

class HttpAsynGet
{
public HttpAsynGet(string url)
{
this.Url = url;
request = (HttpWebRequest)WebRequest.Create(Url);
}
public string Url
{
get;
set;
}
private HttpWebRequest request;
public HttpWebRequest Request
{
get
{
return request;
}
}
public void DO()
{
request.BeginGetResponse(ProcessResponse, this);
}
private void ProcessResponse(IAsyncResult ar)
{
HttpAsynGet item = (HttpAsynGet)ar.AsyncState;
using (HttpWebResponse response = (HttpWebResponse)item.request.EndGetResponse(ar))
{
if (response.StatusCode == HttpStatusCode.OK)
{
Console.WriteLine(response.ContentLength);
Console.WriteLine(new string('=', 20));
}
else
{
Console.WriteLine(response.StatusDescription);
}
this.request.Abort();
}
}
}

调用:
HttpAsynGet get = new HttpAsynGet("xxxxxx");
get.DO();
即可。
在.Net FrameWork的一些类中,只要有以Begin..../End....开头的方法说明该类的这些方法支持异步操作。

.net中的Provider模式

2008-09-23

自.net 2.0出世时,Provider模式就随处可见了,如:MembershipProvider、SiteMapProvider等,
它的出现使我们的应用程序有了更大的扩展性,可以是一个数据工厂的提供者,也可以是一个逻辑处理的提供者。
而实现这种模式却是相当的简单,只需实现以下四步即可:
1、定义一个类,抽象出我们所需要的操作,基类为ProviderBase
如:

public abstract class RssProvider : ProviderBase
{
public abstract void LoadRss();
public abstract void AddRss();
}

2、实现一个Section,用来从配置文件中读取Provider的相关配置,该类继承于ConfigurationSection

public class RssProviderSection : ConfigurationSection
{
[ConfigurationProperty("providers")]
public ProviderSettingsCollection Providers
{
get { return (ProviderSettingsCollection)base["providers"]; }
set { base["providers"] = value; }
}

[StringValidator(MinLength=1)]
[ConfigurationProperty("defaultProvider", DefaultValue = "XmlProvider")]
public string DefaultProvider
{
get { return (string)base["defaultProvider"]; }
set { base["defaultProvider"] = value; }
}
}
public class RssProviderCollection : ProviderCollection
{
//从索引器中获取指定名称的Provider
public new RssProvider this[string name]
{
get
{
return (RssProvider)base[name];
}
}

public override void Add(ProviderBase provider)
{
if (provider == null)
{
throw new ArgumentNullException("provider");
}
if (provider is RssProvider)
{
base.Add(provider);
}
}
}

3、在调用时用RssProviderSection去读取配置文件,并加载指定的Provider

public class RssService
{
private static RssProvider _provider;
private static RssProviderCollection _providers;
private static readonly object obj = new object();
public static RssProvider Provider
{
get
{
LoadProvider();
return _provider;
}
}
public static RssProviderCollection Providers
{

get {
LoadProvider();
return _providers; }
}

public static void LoadProvider()
{
if (_provider == null)
{
lock (obj)
{
if (_provider == null)
{
RssProviderSection section = (BlogProviderSection)System.Configuration.ConfigurationManager.GetSection("Blog/RssService");
_providers = new RssProviderCollection();
ProvidersHelper.InstantiateProviders(section.Providers, _providers, typeof(BlogProvider));
_provider = _providers[section.DefaultProvider];
if (_provider == null)
throw new ProviderException("null");
}
}
}
}
}

4、配置文件

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<sectionGroup name="Blog">
<section name="RssService" type="ClassLib.BlogProviderSection,ClassLib"/>
</sectionGroup>
</configSections>
<Blog>
<RssService defaultProvider="XmlProvider">
<providers>
<add name="XmlProvider" type="ClassLib.XmlProvider,ClassLib"></add>
<add name="SqlProvider" type="ClassLib.SqlProvider,ClassLib"></add>
</providers>
</RssService>
</Blog>
</configuration>

更多内容可以去MSDN查阅ProviderBase基类

天气预报控件

2008-09-17

数据源取自:http://weather.msn.com/

原本是打算直接获取页面源代码,然后分析出需要的结果。
但这有二个弊端:
1、如果页面结构改了,我就得重写代码;
2、图片地址要转成绝对路径。
不小心让我发现msnweather提供了每个城市的天气预报rss
遂直接取rss内容即可。
直接将控件放到工具箱, 拖放到.aspx页面上,设置RSS地址即可
rss地址可以到这里来获取:
http://weather.msn.com/region.aspx?wealocations=China
可以有多个控件,设置不同的rss地址即可显示多个不同城市的天气预报。
理论上天气预报会缓存10分钟
效果:

文件下载:
WeatherLib.dll (12.00 kb)