using System;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using NewLife.Log;
using NewLife.Reflection;
using NewLife.Web;
using XCode;
using XCode.Accessors;
using XCode.Configuration;
using XCode.Membership;
namespace NewLife.CommonEntity.Web
{
/// <summary>第二代实体表单,在Page_Load之前给表单控件赋值,在Page_Load之后从表单控件取值并保存</summary>
/// <remarks>
/// 在PreLoad阶段处理表单,SetForm把实体对象的字段数据设置到表单控件上,GetForm把表单控件的数据取回到实体字段中。
/// 之所以选择PreLoad阶段,是因为这是在Page_Load之前最早能拿到Request等数据的阶段,这样子使用者就可以在Page_Load中处理已经赋值完成的表单。
/// 保存表单时,PreLoad阶段仅仅是给保存按钮设置点击事件,真正的保存动作在点击事件里面,因为在保存表单之前,使用者可能在Page_Load里面对控件进行处理。
/// 所以,在Page_Load之前给表单控件赋值,在Page_Load之后从表单控件取值并保存。
///
/// 另外,DropDownList等数据绑定控件绑定ObjectDataSource时,会在OnPreRender阶段执行绑定,其中的开关是RequiresDataBinding。
/// 绑定方法DataBind会导致RequiresDataBinding=false,这样子人工DataBind之后,控件就不会在OnPreRender阶段自动绑定了。
/// 实体表单在SetForm时有个机制,如果遇到数据绑定的列表控件,会调用一次DataBind,取得列表值,然后再赋值,本以为这样子可以避免OnPreRender阶段的自动绑定。
/// 经查实,数据绑定控件会在PreLoad时间里面检查页面,如果页面不是回发或者关闭了ViewState,都会导致RequiresDataBinding=true。
/// 数据绑定控件在OnInit阶段绑定PreLoad事件,而EntityForm在OnPreInit阶段绑定,比数据绑定控件更早,导致了EntityForm的PreLoad先执行,先DataBind,而后控件还是会设置RequiresDataBinding=true。
/// 因此,EntityForm调整为在InitComplete阶段绑定PreLoad事件。
/// </remarks>
public class EntityForm2 : IEntityForm
{
#region 属性
private Control _Container;
/// <summary>容器</summary>
public Control Container
{
get { return _Container; }
set { _Container = value; }
}
private Type _EntityType;
/// <summary>实体类型</summary>
public Type EntityType
{
get { return _EntityType; }
set { _EntityType = value; }
}
private String _ItemPrefix = "frm";
/// <summary>表单项名字前缀,默认frm</summary>
public virtual String ItemPrefix
{
get { return _ItemPrefix; }
set { _ItemPrefix = value; Accessor = null; }
}
#endregion
#region 构造
/// <summary>实例化一个实体表单</summary>
public EntityForm2() { }
/// <summary>指定控件容器和实体类型,实例化一个实体表单</summary>
/// <param name="container"></param>
/// <param name="type">类型</param>
public EntityForm2(Control container, Type type)
{
(this as IEntityForm).Init(container, type);
}
#endregion
#region 扩展属性
private IEntityAccessor _Accessor;
/// <summary>访问器</summary>
public IEntityAccessor Accessor
{
get
{
if (_Accessor == null)
{
_Accessor = EntityAccessorFactory.Create(EntityAccessorTypes.WebForm)
.SetConfig(EntityAccessorOptions.AllFields, true)
.SetConfig(EntityAccessorOptions.Container, Container)
.SetConfig(EntityAccessorOptions.ItemPrefix, ItemPrefix);
}
return _Accessor;
}
set { _Accessor = value; }
}
private IEntityOperate _Factory;
/// <summary>实体操作者</summary>
public IEntityOperate Factory { get { return _Factory ?? (_Factory = EntityFactory.CreateOperate(EntityType)); } set { _Factory = value; } }
/// <summary>页面</summary>
protected Page Page { get { return Container.Page; } }
/// <summary>响应</summary>
protected HttpResponse Response { get { return HttpContext.Current.Response; } }
private Control _SaveButton;
/// <summary>保存按钮,查找名为btnSave或UpdateButton(兼容旧版本)的按钮</summary>
protected virtual Control SaveButton
{
get
{
if (_SaveButton != null) return _SaveButton;
_SaveButton = FindControl("btnSave");
if (_SaveButton == null) _SaveButton = FindControl("UpdateButton");
//// 随便找一个按钮
//Button btn = ControlHelper.FindControl<Button>(Container, null);
//if (btn != null && btn.UseSubmitBehavior) return btn;
return _SaveButton;
}
set { _SaveButton = value; }
}
private Control _CopyButton;
/// <summary>保存按钮,查找名为btnCopy的按钮</summary>
protected virtual Control CopyButton
{
get
{
if (_CopyButton != null) return _CopyButton;
_CopyButton = FindControl("btnCopy");
return _CopyButton;
}
set { _CopyButton = value; }
}
/// <summary>是否空主键</summary>
public virtual Boolean IsNew
{
get
{
Type type = Factory.Unique.Type;
Object eid = EntityID;
if (type == typeof(Int32) || type == typeof(Int16) || type == typeof(Int64))
return eid != null ? Convert.ToInt64(eid) <= 0 : true;
else if (type == typeof(String))
return eid != null ? String.IsNullOrEmpty((String)eid) : true;
else
throw new NotSupportedException("仅支持整数和字符串类型!");
}
}
private Boolean _CanSave = true;
/// <summary>是否有权限保存数据</summary>
public virtual Boolean CanSave { get { return _CanSave; } set { _CanSave = value; } }
private IManagePage _ManagePage;
/// <summary>管理页。用于控制权限</summary>
public virtual IManagePage ManagePage { get { return _ManagePage ?? (_ManagePage = CommonService.Get<IManagePage>()); } set { _ManagePage = value; } }
#endregion
#region 事件
/// <summary>获取数据实体,允许页面重载改变实体</summary>
public event EventHandler<EntityFormEventArgs> OnGetEntity;
/// <summary>把实体数据设置到表单后触发</summary>
public event EventHandler<EntityFormEventArgs> OnSetForm;
/// <summary>从表单上读取实体数据后触发</summary>
public event EventHandler<EntityFormEventArgs> OnGetForm;
/// <summary>验证时触发</summary>
public event EventHandler<EntityFormEventArgs> OnValid;
/// <summary>保存前触发,位于事务保护内</summary>
public event EventHandler<EntityFormEventArgs> OnSaving;
/// <summary>保存成功后触发,位于事务保护外</summary>
public event EventHandler<EntityFormEventArgs> OnSaveSuccess;
/// <summary>保存失败后触发,位于事务保护外</summary>
public event EventHandler<EntityFormEventArgs> OnSaveFailure;
#endregion
#region 实体相关
private String _KeyName;
/// <summary>键名。使用者可以通过给KeyName置空来避免内部自动根据Request[KeyName]取值</summary>
public virtual String KeyName
{
get
{
if (_KeyName != null) return _KeyName;
if (Factory.Unique != null)
_KeyName = Factory.Unique.Name;
else
{
FieldItem[] fis = Factory.Table.PrimaryKeys;
if (fis != null && fis.Length > 1)
{
if (XTrace.Debug) XTrace.WriteLine("实体表单默认不支持多主键(实体类{0}),需要手工给Entity赋值!", EntityType.Name);
}
}
return _KeyName;
}
set { _KeyName = value; }
}
/// <summary>主键。如果实体已经存在,则使用实体的主键值。因为有些时候实体是由外部赋值的</summary>
public virtual Object EntityID
{
get
{
// 如果实体已经存在,则使用实体的主键值。
if (_Entity != null && Factory.Unique != null) return _Entity[Factory.Unique.Name];
// 使用者可以通过给KeyName置空来避免内部自动根据Request[KeyName]取值
if (String.IsNullOrEmpty(KeyName)) return null;
String str = HttpContext.Current.Request[KeyName];
if (String.IsNullOrEmpty(str)) return null;
FieldItem fi = Factory.Unique;
if (fi != null)
{
Type type = Factory.Unique.Type;
if (type == typeof(Int32) || type == typeof(Int64))
{
Int32 id = 0;
if (!Int32.TryParse(str, out id)) id = 0;
return (Object)id;
}
else if (type == typeof(String))
{
return (Object)str;
}
}
throw new NotSupportedException("仅支持整数和字符串类型!");
}
}
private Boolean _isGettingEntity;
private IEntity _Entity;
/// <summary>数据实体。使用者可以通过给KeyName置空来避免内部自动根据Request[KeyName]取值</summary>
public virtual IEntity Entity
{
get
{
if (_Entity == null && !_isGettingEntity)
{
_isGettingEntity = true;
Object eid = EntityID;
// 使用者可以通过给KeyName置空来避免内部自动根据Request[KeyName]取值
if (!String.IsNullOrEmpty(KeyName)) _Entity = Factory.FindByKeyForEdit(eid);
// 外部可以通过OnGetEntity时间直接修改Entity
if (OnGetEntity != null) OnGetEntity(this, new EntityFormEventArgs());
if (_Entity == null && !String.IsNullOrEmpty(KeyName)) _Entity = Factory.FindByKeyForEdit(eid);
// 把Request参数读入到实体里面
FillEntityWithRequest(_Entity);
//将主键脏数据标记为不修改
_Entity.Dirtys.Remove(Factory.Unique.Name);
_isGettingEntity = false;
}
return _Entity;
}
set { _Entity = value; }
}
/// <summary>使用Request参数填充entity</summary>
/// <param name="entity"></param>
protected virtual void FillEntityWithRequest(IEntity entity)
{
if (entity == null || HttpContext.Current == null || HttpContext.Current.Request == null) return;
// 借助Http实体访问器,直接把Request参数读入到实体里面
IEntityAccessor accessor = EntityAccessorFactory.Create(EntityAccessorTypes.Http);
// 这里的异常不需要暴露到外部
try
{
accessor.Read(entity);
}
catch { }
}
#endregion
#region 生命周期
Boolean hasInit = false;
private void Init()
{
if (hasInit) return;
hasInit = true;
Page.InitComplete += new EventHandler(Page_InitComplete);
//Page.PreLoad += new EventHandler(OnPreLoad);
//Page.LoadComplete += new EventHandler(OnLoadComplete);
}
private void Page_InitComplete(object sender, EventArgs e)
{
Page.PreLoad += new EventHandler(OnPreLoad);
}
/// <summary></summary>
/// <param name="sender"></param>
/// <param name="e"></param>
protected virtual void OnPreLoad(object sender, EventArgs e)
{
IEntity entity = null;
try
{
entity = Entity;
}
catch (XCodeException ex)
{
// 由下面自行处理,而不是抛出这个异常
if (!ex.Message.Contains("参数错误!无法取得编号为")) throw;
}
// 判断实体
if (entity == null)
{
String msg = null;
Object eid = EntityID;
if (IsNew)
msg = String.Format("参数错误!无法取得编号为{0}的{2}({1})!可能未设置自增主键!", eid, Factory.TableName, Factory.Table.Description);
else
msg = String.Format("参数错误!无法取得编号为{0}的{2}({1})!", eid, Factory.TableName, Factory.Table.Description);
WebHelper.Alert(msg);
Response.Write(msg);
Response.End();
return;
}
Control btn = SaveButton;
Control btncopy = CopyButton;
if (!Page.IsPostBack)
{
if (ManagePage != null && ManagePage.Container != null && ManagePage.ValidatePermission)
{
CanSave = entity.IsNullKey && ManagePage.Acquire(PermissionFlags.Insert) || ManagePage.Acquire(PermissionFlags.Update);
// 复制只需要新增权限
if (btncopy != null) btncopy.Visible = ManagePage.Acquire(PermissionFlags.Insert);
}
// 新增数据时,不显示复制按钮
if (IsNew && btncopy != null) btncopy.Visible = false;
if (btn != null)
{
btn.Visible = CanSave;
if (btn is IButtonControl) (btn as IButtonControl).Text = entity.IsNullKey ? "新增" : "更新";
}
//利用js控制按钮点击状态
//2013-1-14 @宁波-小董,注释下面2句:
//原因:这里在XCode默认网站后台没有问题,但在其他网站后台,如果利用js对表单进行验证,就会出现错误,
//验证不通过,这里也会执行js代码,“正在提交”,然后页面就死掉了
//不知道要怎么修改才能使得页面验证不通过时,这个就不执行。
//RegButtonOnClientClick(btn);
//RegButtonOnClientClick(btncopy);
SetForm();
}
else
{
// 如果外部设置了按钮事件,则这里不再设置
if (btn != null && btn is IButtonControl && ControlHelper.FindEventHandler(btn, "Click") == null)
(btn as IButtonControl).Click += delegate
{
GetForm();
if (ValidForm()) SaveFormWithTrans();
};
// 这里还不能保存表单,因为使用者习惯性在Load事件里面写业务代码,所以只能在Load完成后保存
//else if (Page.AutoPostBackControl == null)
//{
// GetForm();
// if (ValidForm()) SaveFormWithTrans();
//}
if (btncopy != null && btncopy is IButtonControl && ControlHelper.FindEventHandler(btncopy, "Click") == null)
(btncopy as IButtonControl).Click += delegate
{
GetForm();
// 清空主键,变成新增
IEntityOperate eop = EntityFactory.CreateOperate(Entity.GetType());
foreach (var item in eop.Fields)
{
if (item.PrimaryKey || item.IsIdentity) Entity[item.Name] = null;
}
if (ValidForm()) SaveFormWithTrans();
};
}
}
//void OnLoadComplete(object sender, EventArgs e)
//{
// if (Page.IsPostBack && Page.AutoPostBackControl == null)
// {
// Control btn = SaveButton;
// if (btn == null || !(btn is IButtonControl))
// {
// Accessor.Read(Entity);
// if (ValidForm()) SaveFormWithTrans();
// }
// }
//}
#endregion
#region 方法
/// <summary>把实体的属性设置到控件上</summary>
public virtual void SetForm()
{
Accessor.OnWriteItem += new EventHandler<EntityAccessorEventArgs>(Accessor_OnWrite);
Accessor.Write(Entity);
if (OnSetForm != null) OnSetForm(this, new EntityFormEventArgs());
}
private void Accessor_OnWrite(object sender, EntityAccessorEventArgs e)
{
WebControl wc = ControlHelper.FindControlInPage<WebControl>(ItemPrefix + e.Field.Name);
if (wc == null) return;
if (!CanSave)
{
if (wc is TextBox)
(wc as TextBox).ReadOnly = !CanSave;
else
wc.Enabled = CanSave;
}
}
/// <summary>从表单上读取实体数据</summary>
public virtual void GetForm()
{
Accessor.Read(Entity);
if (OnGetForm != null) OnGetForm(this, new EntityFormEventArgs());
}
/// <summary>验证表单,返回是否有效数据,决定是否保存表单数据</summary>
/// <returns></returns>
public virtual Boolean ValidForm()
{
foreach (FieldItem item in Factory.Fields)
{
Control control = FindControlByField(item);
if (control == null) continue;
if (!ValidFormItem(item, control)) return false;
}
if (OnValid != null)
{
var e = new EntityFormEventArgs() { Cancel = false };
OnValid(this, e);
if (e.Cancel) return false;
}
return true;
}
/// <summary>验证表单项</summary>
/// <param name="field">字段</param>
/// <param name="control"></param>
/// <returns></returns>
protected virtual Boolean ValidFormItem(FieldItem field, Control control)
{
// 必填项
if (!field.IsNullable)
{
if (field.Type == typeof(String))
{
if (String.IsNullOrEmpty((String)Entity[field.Name]))
{
WebHelper.Alert(String.Format("{0}不能为空!", field.DisplayName));
if (!(control is HiddenField)) control.Focus();
return false;
}
}
else if (field.Type == typeof(DateTime))
{
DateTime d = (DateTime)Entity[field.Name];
if (d == DateTime.MinValue || d == DateTime.MaxValue)
{
WebHelper.Alert(String.Format("{0}不能为空!", field.DisplayName));
if (!(control is HiddenField)) control.Focus();
return false;
}
}
}
return true;
}
private void SaveFormWithTrans()
{
var session = Factory.Session;
session.BeginTrans();
Exception _ex = null;
try
{
Boolean cancel = false;
if (OnSaving != null)
{
var e = new EntityFormEventArgs() { Cancel = false };
//表单OnSaving事件取消仅仅是用于用户已经提前保存过表单,防止重复保存的情况
//所以即使取消保存,依然会正常的进如数据保存成功提示
//如果需要进行数据校验,请在OnValid中进行
OnSaving(this, e);
if (e.Cancel) cancel = true;
}
if (!cancel) SaveForm();
session.Commit();
}
catch (Exception ex)
{
session.Rollback();
_ex = ex;
}
try
{
if (_ex == null)
{
SaveFormSuccess();
}
else
{
SaveFormFailure(_ex);
}
}
catch (Exception ex)
{
XTrace.WriteException(ex);
Page.ClientScript.RegisterStartupScript(this.GetType(), "SaveFormError",
"alert('保存后触发" + _ex == null ? "完成" : "失败" + "事件发生了异常,请检查日志!');",
true);
}
}
/// <summary>保存表单,把实体保存到数据库</summary>
void IEntityForm.SaveForm() { SaveFormWithTrans(); }
/// <summary>保存表单,把实体保存到数据库,当前方法处于事务保护之中</summary>
protected virtual void SaveForm() { Entity.Save(); }
/// <summary>保存成功</summary>
protected virtual void SaveFormSuccess()
{
if (OnSaveSuccess != null)
{
var e = new EntityFormEventArgs() { Cancel = false };
OnSaveSuccess(this, e);
if (e.Cancel) return;
}
// 这个地方需要考虑一个问题,就是列表页查询之后再打开某记录进行编辑,编辑成功后,如果强行的reload列表页,浏览器会循环是否重新提交
// 经测试,可以找到列表页的那个查询按钮,模拟点击一次它,实际上就是让ASP.Net列表页回发一次,可以解决这个问题
Page.ClientScript.RegisterStartupScript(this.GetType(), "alert", @"alert('成功!');
(function(){
var load=window.onload;
window.onload=function(){
try{
if(load) load();
parent.Dialog.CloseAndRefresh(frameElement);
}catch(e){};
};
})();
", true);
}
/// <summary>保存失败</summary>
/// <param name="ex"></param>
protected virtual void SaveFormFailure(Exception ex)
{
if (OnSaveFailure != null)
{
var e = new EntityFormEventArgs() { Cancel = false, Error = ex };
OnSaveFailure(this, e);
if (e.Cancel) return;
}
// 如果是参数异常,参数名可能就是字段名,可以定位到具体控件
ArgumentException ae = ex as ArgumentException;
if (ae != null && !String.IsNullOrEmpty(ae.ParamName))
{
Control control = FindControl(ItemPrefix + ae.ParamName);
if (control != null && !(control is HiddenField)) control.Focus();
}
WebHelper.Alert("失败!" + ex.Message);
}
/// <summary>
/// 设置保存按钮名称
/// </summary>
/// <param name="text"></param>
public virtual void SetSaveButtonText(String text)
{
SetControlMemberValue(SaveButton, "Text", text);
}
/// <summary>
/// 设置另存为按钮名称
/// </summary>
/// <param name="text"></param>
public virtual void SetCopyButtonText(String text)
{
SetControlMemberValue(CopyButton, "Text", text);
}
#endregion
#region 辅助
/// <summary>查找表单控件</summary>
/// <param name="id"></param>
/// <returns></returns>
protected virtual Control FindControl(string id)
{
Control control = ControlHelper.FindControlByField<Control>(Container, id);
if (control != null) return control;
control = Container.FindControl(id);
if (control != null) return control;
return ControlHelper.FindControl<Control>(Container, id);
}
/// <summary>查找字段对应的控件</summary>
/// <param name="field">字段</param>
/// <returns></returns>
protected virtual Control FindControlByField(FieldItem field)
{
return FindControl(ItemPrefix + field.Name);
}
/// <summary>
/// 设置按钮状态脚本
/// </summary>
/// <param name="btn"></param>
protected virtual void RegButtonOnClientClick(Control btn)
{
//利用js控制按钮点击状态
if (btn != null && btn.Visible == true)
{
String btnClientOnclick = "javascript:var self=this;setTimeout(function(){self.disabled=true;self.value='正在提交'},0);";
if (btn is Button || btn is LinkButton)
SetControlMemberValue(btn, "OnClientClick", btnClientOnclick);
//if (btn is Button)
// (btn as Button).OnClientClick = btnClientOnclick;
//else if (btn is LinkButton)
// (btn as LinkButton).OnClientClick = btnClientOnclick;
}
}
/// <summary>
/// 设置控件成员值
/// </summary>
/// <param name="control"></param>
/// <param name="fieldName"></param>
/// <param name="text"></param>
private void SetControlMemberValue(Control control, String fieldName, String text)
{
if (control == null) return;
//NewLife.Reflection.MemberInfoX.Create(control.GetType(), fieldName).SetValue(control, text);
control.SetValue(fieldName, text);
}
#endregion
#region IEntityForm 成员
/// <summary>使用控件容器和实体类初始化接口</summary>
/// <param name="container"></param>
/// <param name="entityType"></param>
IEntityForm IEntityForm.Init(Control container, Type entityType)
{
if (container == null)
{
if (HttpContext.Current.Handler is Page) container = HttpContext.Current.Handler as Page;
}
Container = container;
EntityType = entityType;
//获取到的ManagePage.Container为空,需要重新赋值
ManagePage.Container = container;
Init();
return this;
}
#endregion
}
}
|