在附件管理模块中加进对FTP 上传和预览的辅助

在事先介绍的附件管理模块里面《Winform开发框架之通用附件管理模块》以及《Winform开发框架之附件管理采用》,介绍了附件的保管效率,通过对数据库记录的处理和文书的管制,实现了附件文件和著录的结合管理,可以选择在单机版的WInform框架,也得以使用在分布式的混合式开发框架中,随着部分支付境况的丰裕,大家需要以FTP格局上传文件,由此对这多少个附件管理模块举办扩展,以便适合更多的骨子里项目需求。

1.备忘录情势的概念及拔取情状
备忘录形式是一种表现形式,该情势用于保存对象当前景观,并且在之后可以另行卷土重来到此情景。备忘录形式实现的方法需要确保被封存的目的情状无法被对象从表面访问,目标是为着维护好被保存的这么些目标情状的完整性以及其中贯彻不向外透露。
定义:
在不损坏封闭的前提下,捕获一个目标的中间景色,并在该对象之外保存那么些情景,这样,将来就可将该目的回复到原来保存的情景
应用情况:

1、FTP上传、HTTP文件预览实现思路

咱俩考虑的附件管理,底层都是需要在Winform、Web等支付品种上重用的,因而底层的统筹需要考虑好相应的拍卖,此外前边可以选拔WInform的HTML编辑控件、或者Web的HTML编辑控件举办集成,附件则是联合在一个零部件里面实现的。

依傍FTP的公文上传,大家单机版本或者按照局域网的Winform界面程序,也可以独自构建一个FTP服务器,实现文件的共享;而分布式的混合式开发框架中,对于文本的上传,可以挑选基于服务的文件系统写入,同时也足以依照FTP的章程上传。

据悉混合式框架的FTP形式上传文件,其逻辑关系如下所示。

 图片 1

这般文件通过FTP情势上传的文件系统后,我们在文件系统里面搭建一个HTTP服务,这样对应上的HTTP地址就能够实现文件的下载,以及图片的查看等操作了(可以在HTML编辑器中贯彻)。

 

内需保留一个目的在某一个时刻的场地或一些情况
亟待用一个接口来让任何对象得到这个情状,将会暴露目的的实现细节并破坏对象的封装性,一个对象不指望外面直接访问其中间景观,通过中间对象足以间接访问其里面意况

2、引入FTP组件实现文件上传

 使用FTP上传,即使在和谐的公用类库里面有FTPHelper类能够接纳,不过相对来说,我更愿意引入更为周全强大的FTP开源组件进行有关的拍卖,这里我们利用FluentFTP这几个组件(GitHub地址:https://github.com/hgupta9/FluentFTP
),这些是一个用到很广,功用很强大的FTP组件。

FluentFTP是一款老外开发的基于.Net的辅助FTP及的FTPS
的FTP类库,FluentFTP是完全托管的FTP客户端,被规划为便于使用和易于扩充。它扶助文件和目录列表,上传和下载文件和SSL
/ TLS连接。它可以接连到Unix和Windows
IIS建立FTP服务器。这些序列是完全开发托管C #。

其一组件的应用代码,这里粘贴一下,以便总体有一个直观的打听呢。

// create an FTP client
FtpClient client = new FtpClient("123.123.123.123");

// if you don't specify login credentials, we use the "anonymous" user account
client.Credentials = new NetworkCredential("david", "pass123");

// begin connecting to the server
client.Connect();

// get a list of files and directories in the "/htdocs" folder
foreach (FtpListItem item in client.GetListing("/htdocs")) {

    // if this is a file
    if (item.Type == FtpFileSystemObjectType.File){

        // get the file size
        long size = client.GetFileSize(item.FullName);

    }

    // get modified date/time of the file or folder
    DateTime time = client.GetModifiedTime(item.FullName);

    // calculate a hash for the file on the server side (default algorithm)
    FtpHash hash = client.GetHash(item.FullName);

}

// upload a file
client.UploadFile(@"C:\MyVideo.mp4", "/htdocs/big.txt");

// rename the uploaded file
client.Rename("/htdocs/big.txt", "/htdocs/big2.txt");

// download the file again
client.DownloadFile(@"C:\MyVideo_2.mp4", "/htdocs/big2.txt");

// delete the file
client.DeleteFile("/htdocs/big2.txt");

// delete a folder recursively
client.DeleteDirectory("/htdocs/extras/");

// check if a file exists
if (client.FileExists("/htdocs/big2.txt")){ }

// check if a folder exists
if (client.DirectoryExists("/htdocs/extras/")){ }

// upload a file and retry 3 times before giving up
client.RetryAttempts = 3;
client.UploadFile(@"C:\MyVideo.mp4", "/htdocs/big.txt", FtpExists.Overwrite, false, FtpVerify.Retry);

// disconnect! good bye!
client.Disconnect();

有了那一个了解,我们在通常Winform程序仍然混合式框架的的先后中,大家通过安排指定FTP的相干消息,就可以在代码里面加载那多少个音讯,举行FTP的登陆、文件上传、下载等操作了。

 

Paste_Image.png

3、附件管理模块实现

有了上边的思路和零部件的襄助,我们对原本的附件管理模块举行连锁的升级处理即可兑现FTP上传情势的拍卖了。

第一为了有利于,我们先定义一个拿走FTP服务器、用户名、密码等参数的布置实体类,如下所示。

    /// <summary>
    /// FTP配置信息
    /// </summary>
    [DataContract]
    [Serializable]
    public class FTPInfo
    {
        /// <summary>
        /// 默认构造函数
        /// </summary>
        public FTPInfo()
        {

        }

        /// <summary>
        /// 参数化构造函数
        /// </summary>
        /// <param name="server"></param>
        /// <param name="user"></param>
        /// <param name="password"></param>
        public FTPInfo(string server, string user, string password, string baseUrl)
        {
            this.Server = server;
            this.User = user;
            this.Password = password;
            this.BaseUrl = baseUrl;
        }

        /// <summary>
        /// FTP服务地址
        /// </summary>
        [DataMember]
        public string Server { get; set; }

        /// <summary>
        /// FTP用户名
        /// </summary>
        [DataMember]
        public string User { get; set; }

        /// <summary>
        /// FTP密码
        /// </summary>
        [DataMember]
        public string Password { get; set; }

        /// <summary>
        /// FTP的基础路径,如可以指定为IIS的路径:http://www.iqidi.com:8000 ,方便下载打开
        /// </summary>
        [DataMember]
        public string BaseUrl { get; set; }
    }

概念一个函数,专门用来领取配置文件之中的相关FTP参数的,如下所示。

        /// <summary>
        /// 获取配置的FTP配置参数
        /// </summary>
        /// <returns></returns>
        private FTPInfo GetFTPConfig()
        {
            var ftp_server = config.AppConfigGet("ftp_server");
            var ftp_user = config.AppConfigGet("ftp_user");
            var ftp_pass = config.AppConfigGet("ftp_password");
            var ftp_baseurl = config.AppConfigGet("ftp_baseurl");

            return new FTPInfo(ftp_server, ftp_user, ftp_pass, ftp_baseurl);
        }

其中大家的配置文件如下所示。

图片 2

应用FluentFTP的零件代码如下所示。

//使用FluentFTP操作FTP文件
FtpClient client = new FtpClient(ftpInfo.Server, ftpInfo.User, ftpInfo.Password);

接下来调用FTP组件对目录举行判断,无则开创一个即可。

//确定日期时间目录(格式:yyyy-MM),不存在则创建
string savePath = string.Format("/{0}-{1:D2}/{2}", DateTime.Now.Year, DateTime.Now.Month, category);
bool isExistDir = client.DirectoryExists(savePath);
if(!isExistDir)
{
    client.CreateDirectory(savePath);
}

最后采用组件上传文件即可,那里上传文件,由于后边FileUploadInfo实体类里面储存的是字节数组,由此也是运用FTP组件直接上传字节数组即可。

//使用FTP上传文件
//避免文件重复,使用GUID命名
var ext = FileUtil.GetExtension(info.FileName);
var newFileName = string.Format("{0}{1}", Guid.NewGuid().ToString(), ext);//FileUtil.GetFileName(file);

savePath = savePath.UriCombine(newFileName);
bool uploaded = client.Upload(info.FileData, savePath, FtpExists.Overwrite, true);

文件上传到文件服务器后,剩下的就是把相关的新闻囤积到附件管理模块的数据表里面即可,这样可以在应用的时候,直接行使数据库里面的消息,假如是急需查阅图片或者下载文件,那么拼接好相关的HTTP地址即可,我们来看望对应的数据库记录截图如下所示。

图片 3

有了这个基础音信,我们得以同时改造自己事先介绍过的Winform之HTML编辑控件:ZetaHtmlEditControl了(分享一个Winform里面的HTML编辑控件Zeta
HTML Edit
Control,汉化附源码
),我对这多少个控件所有英文的菜谱、工具栏、对话框、指示内容等资源拓展中文化后,并在工具栏中加进插入图片、打印效率后,界面如下所示。

图片 4

默认情形下,我们投入图片的形式,肯定依然基于本地文件的法子了;可是经过我们改造使用FTP上传文件措施后,在控件上取得HTTP地址,就足以对图纸文件举行预览呈现的操作了。

这种艺术社团的图样地址,属于标准的URL地址,可以在逐一地点开展查看的,如下界面所示。

图片 5

以此就是ZetaHtmlEditControl控件,整合我们面前早已完成了FTP上传情势的附件管理模块,实现编辑在线HTML的效果,这样的HTML内容,同样可以适合在Web界面下的HTML编辑器上展开显示了。

如上就是自己为全体WInform开发框架结构的类型组件,扩张的FTP上传格局,同时完善了相应的光景需求,在ZetaHtmlEditControl控件上落实编辑在线HTML的职能,希望开发的笔触对你抱有增益。

 

2. 备忘录形式的得失
2.1优点
给用户提供了一种可以回复境况的机制,能够使用户可以相比较便于地回到某个历史的情况
贯彻了音信的包裹,使得用户不需要关注状态的涵养细节
2.2缺点
消耗资源,假如类的积极分子变量过多,势必会占用相比较大的资源,而且每趟保存都会消耗一定的内存

3.注意事项

备忘录的人命期备忘录创立出来就要在“最近”的代码中使用,要积极管理它的生命周期,建立就要选择,不接纳就要立刻删除其引用,等待垃圾回收器对它的回收处理
备忘录的特性不要在频繁建立备份的面貌中使用备忘录情势(比如一个for循环中),紧要缘由是一是控制不了备忘录建立的靶子数量;而是大目标的确立是要消耗资源的,系统的特性需要考虑。
4. 备忘录情势的落实情势

public class Memento {
    //发起人的内部状态
    private String state="";
    public Memento(String state) {
        this.state = state;
    }
    public String getState() {
        return state;
    }
    public void setState(String state) {
        this.state = state;
    }
}```

public class Caretaker {
//备忘录对象
private Memento memento;
public Memento getMemento() {
return memento;
}
public void setMemento(Memento memento) {
this.memento = memento;
}
}“`

public class Originator {
    //内部状态
    private String state = "";
    public String getState() {
        return state;
    }
    public void setState(String state) {
        this.state = state;
    }
    //创建一个备忘录
    public Memento createMemento() {
        return new Memento(state);
    }
    //恢复一个备忘录
    public void restoreMemento(Memento memento) {
        this.setState(memento.getState());
    }
}```

public class Test {
public static void main(String args[]) {
//定义出发起人
Originator originator = new Originator();
originator.setState(“初始:1111111111111”);
//定义出备忘录管理员
Caretaker caretaker = new Caretaker();
System.out.println(originator.getState());
//创设一个备忘录
caretaker.setMemento(originator.createMemento());
originator.setState(“改变:22222222222222”);
System.out.println(originator.getState());
originator.setState(“恢复:restoreMemento”);
originator.restoreMemento(caretaker.getMemento());
System.out.println(originator.getState());
}
}“`
5. 备忘录情势在Android中的实际利用
在Android开发中,状态形式采取是Android中的状态保持,也就是中间的onSaveInstanceState和onRestoreInstanceState。当Activity不是例行艺术退出,且Activity在跟着的岁月内被系统杀死此前会调用这多少个点子让开发人士可以有机会存储Activity的连带消息,并且在下次放好Activity的时候恢复生机这么些数量。
Activity:

protected void onSaveInstanceState(Bundle outState) {
 outState.putBundle(WINDOW_HIERARCHY_TAG, mWindow.saveHierarchyState()); //保存当前窗口的视图树的状态
 Parcelable p = mFragments.saveAllState(); //存储Fragment的状态
 if (p != null) {
 outState.putParcelable(FRAGMENTS_TAG, p);
 }
 getApplication().dispatchActivitySaveInstanceState(this, outState);
 }```
Window的实现为PhoneWindow:

/** {@inheritDoc} */
@Override
public Bundle saveHierarchyState() {
Bundle outState = new Bundle();
if (mContentParent == null) {
return outState;
}
//通过SparseArray类来储存,这一定于一个key为整型的map
SparseArray<Parcelable> states = new
SparseArray<Parcelable>();
//mContentParent就是调用Activity的setContentView函数设置的内容视图,它是内容视图的根节点,在此地存储整棵树的布局
mContentParent.saveHierarchyState(states);
//将视图树结构放到outState中
outState.putSparseParcelableArray(VIEWS_TAG, states);
// 保存当前界面中获取了关键的View
View focusedView = mContentParent.findFocus();
if (focusedView != null) {
if (focusedView.getId() != View.NO_ID) {
outState.putInt(FOCUSED_ID_TAG, focusedView.getId());
} else {
if (false) {
Log.d(TAG, “couldn’t save which view has focus because the focused view

  • focusedView + ” has no id.”);
    }
    }
    }
    // 存储整个面板的景观
    SparseArray<Parcelable> panelStates = new
    SparseArray<Parcelable>();
    savePanelState(panelStates);
    if (panelStates.size() > 0) {
    outState.putSparseParcelableArray(PANELS_TAG, panelStates);
    }
    //存储actionBar的状态
    if (mDecorContentParent != null) {
    SparseArray<Parcelable> actionBarStates = new
    SparseArray<Parcelable>();
    mDecorContentParent.saveToolbarHierarchyState(actionBarStates);
    outState.putSparseParcelableArray(ACTION_BAR_TAG,
    actionBarStates);
    }
    return outState;
    }“`
    在saveHierarchyState函数中,首即便储存了与近日UI、ActionBar相关的View状态。mContentParent就是我们经过Activity的setContentView函数设置的情节视图,他是其一内容视图的根节点。mContentParent是一个ViewGroup对象,可是saveHierachyState是在父类View中

public void saveHierarchyState(SparseArray<Parcelable> container) {
 dispatchSaveInstanceState(container);
 }```

protected void dispatchSaveInstanceState(SparseArray<Parcelable>
container) {
if (mID != NO_ID && (mViewFlags & SAVE_DISABLED_MASK) == 0) {
mPrivateFlags &= ~PFLAG_SAVE_STATE_CALLED;
Parcelable state = onSaveInstanceState();
if ((mPrivateFlags & PFLAG_SAVE_STATE_CALLED) == 0) {
throw new IllegalStateException(
“Derived class did not call super.onSaveInstanceState()”);
}
if (state != null) {
// Log.i(“View”, “Freezing #” + Integer.toHexString(mID)
// + “: ” + state);
container.put(mID, state); //将自身意况放到container中 key 为id
value为本人意况
}
}
}“`

//View类默认的存储的状态为空
 protected Parcelable onSaveInstanceState() {
  mPrivateFlags |= PFLAG_SAVE_STATE_CALLED;
  if (mStartActivityRequestWho != null) {
  BaseSavedState state = new BaseSavedState(AbsSavedState.EMPTY_STATE);
  state.mStartActivityRequestWhoSaved = mStartActivityRequestWho;
  return state;
  }
  return BaseSavedState.EMPTY_STATE;
  }```

恢复数据的调用过程如下,基本流程与保存类似
Activity:

protected void onRestoreInstanceState(Bundle savedInstanceState) {
if (mWindow != null) {
Bundle windowState =
savedInstanceState.getBundle(WINDOW_HIERARCHY_TAG);
if (windowState != null) {
mWindow.restoreHierarchyState(windowState);
}
}
}“`
PhoneWindow:

/** {@inheritDoc} */
 @Override
 public void restoreHierarchyState(Bundle savedInstanceState) {
 if (mContentParent == null) {
 return;
 }
 SparseArray<Parcelable> savedStates
 = savedInstanceState.getSparseParcelableArray(VIEWS_TAG);
 if (savedStates != null) {
 mContentParent.restoreHierarchyState(savedStates);
 }
 // restore the focused view
 int focusedViewId = savedInstanceState.getInt(FOCUSED_ID_TAG, View.NO_ID);
 if (focusedViewId != View.NO_ID) {
 View needsFocus = mContentParent.findViewById(focusedViewId);
 if (needsFocus != null) {
 needsFocus.requestFocus();
 } else {
 Log.w(TAG,
 "Previously focused view reported id " + focusedViewId
 + " during save, but can't be found during restore.");
 }
 }
 // restore the panels
 SparseArray<Parcelable> panelStates = savedInstanceState.getSparseParcelableArray(PANELS_TAG);
 if (panelStates != null) {
 restorePanelState(panelStates);
 }
 if (mDecorContentParent != null) {
 SparseArray<Parcelable> actionBarStates =
 savedInstanceState.getSparseParcelableArray(ACTION_BAR_TAG);
 if (actionBarStates != null) {
 doPendingInvalidatePanelMenu();
 mDecorContentParent.restoreToolbarHierarchyState(actionBarStates);
 } else {
 Log.w(TAG, "Missing saved instance states for action bar views! " +
 "State will not be restored.");
 }
 }
 }```
View:

public void restoreHierarchyState(SparseArray<Parcelable>
container) {
dispatchRestoreInstanceState(container);
}“`

/**
* Called by {@link #restoreHierarchyState(android.util.SparseArray)} to retrieve the
* state for this view and its children. May be overridden to modify how restoring
* happens to a view's children; for example, some views may want to not store state
* for their children.
*
* @param container The SparseArray which holds previously saved state.
*
* @see #dispatchSaveInstanceState(android.util.SparseArray)
* @see #restoreHierarchyState(android.util.SparseArray)
* @see #onRestoreInstanceState(android.os.Parcelable)
*/
protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
if (mID != NO_ID) {
Parcelable state = container.get(mID);
if (state != null) {
// Log.i("View", "Restoreing #" + Integer.toHexString(mID)
// + ": " + state);
mPrivateFlags &= ~PFLAG_SAVE_STATE_CALLED;
onRestoreInstanceState(state);
if ((mPrivateFlags & PFLAG_SAVE_STATE_CALLED) == 0) {
throw new IllegalStateException(
"Derived class did not call super.onRestoreInstanceState()");
}
}
}
}```

protected void onRestoreInstanceState(Parcelable state) {
mPrivateFlags |= PFLAG_SAVE_STATE_CALLED;
if (state != null && !(state instanceof AbsSavedState)) {
throw new IllegalArgumentException(“Wrong state class, expecting View
State but “

  • “received ” + state.getClass().toString() + ” instead. This usually
    happens “
  • “when two views of different type have the same id in the same
    hierarchy. “
  • “This view’s id is ” + ViewDebug.resolveId(mContext, getId()) + “.
    Make sure “
  • “other views do not use the same id.”);
    }
    if (state != null && state instanceof BaseSavedState) {
    mStartActivityRequestWho = ((BaseSavedState)
    state).mStartActivityRequestWhoSaved;
    }
    }“`
    在这一个历程中,Activity扮演了Caretaker角色,负责储存、复苏UI的情况音信;Activity、Fragement、View、ViewGroup等对象为Originator角色,也就是亟需存储状态的靶子;Memento则是由Bundle类扮演。

Activity在悬停此前会依据Activity的脱离情景来抉择是否需要存储状态,在重复起动该Activity时会判断ActivityClientRecord对象中是不是存储了Activity的图景,如若含有状态则调用Activity的onRestoreInstanceState函数,从而使得Activity的UI效果与上次保持一致,这样一来,就确保了在窘迫退出Activity时不会丢掉数据的情况,很好地升级了用户体验。
出处:http://huangjunbin.com/page/2/

Post Author: admin

发表评论

电子邮件地址不会被公开。 必填项已用*标注