ASP.NET MVC 框架中的 Web 部件/Portlet 开发






4.84/5 (38投票s)
在 ASP.NET MVC 框架中,一个解决方案通过拖放 Portlet/Web 部件定制功能进行增强。它利用 jQuery 提供了更好的用户体验,避免了在个性化过程中进行整体页面刷新。
- 下载 MVC Application.zip VS 2013 - 2.1 MB
- 下载 MVC Application.zip VS 2010 - 4.5 MB
- 下载 MVC Application.zip VS 2008 - 803.5 KB
应用程序的实时演示
目录
引言
作为 ASP.NET MVC 框架的爱好者,我曾为 ASP.NET MVC 中的 Portlet 或 Web 部件解决方案而四处寻找,但未能找到理想的解决方案。本次开发旨在实现 ASP.NET MVC 框架中的 Portlet/Web 部件应用程序。它旨在提供带有分区的累积视图,并提供个性化功能。为了尽可能简洁并专注于核心思想,我们采用 ASP.NET 会话和应用程序变量作为存储库,因此定制或个性化的 Portlet/Web 部件调整将无法在多个 ASP.NET 会话中保持。
先决条件
为了理解本文,您需要对 MVC 框架有一定的了解。如果您认为自己具备足够的专业知识,那么您最好继续阅读本文。
如果您尚未设置 ASP.NET MVC,请在继续之前安装以下列出的项目。您也可以使用 Microsoft Web Platform Installer 安装 ASP.NET MVC。
Data Model
首先,理解数据模型至关重要。其实体描述了 Portlet/Web 部件如何与门户协调一致。
类别实体
它保存类别数据,用于将 Portlet/Web 部件划分到不同的类别中。实体与 Portlet
和 Portlet_User
实体之间存在一对多关系。实体将在应用程序启动事件时填充数据,并驻留在 ASP.NET 应用程序变量中。
Portlet 实体
它是 Category
实体的一个子实体,与 Portlet_User
实体之间存在一对多关系。它保存了默认的 Portlet/Web 部件信息,包括它们在门户上的位置。
-
Portlet_ID
:它是实体的主键,用于指定 portlet。 -
Category_ID
:它保存类别 ID,指示 Portlet/Web 部件属于哪个类别 -
Link
:它保存 RSS Feed 的链接 -
Column_No
:描述 Portlet/Web 部件的列号或 web 部件区域。 -
Title
:保存 RSS Feed 标题 -
行序列:在特定列中保存行号
Portlet 用户实体
它是 Category
、Portlet
和 User
实体的子实体。它或多或少是 Portlet
实体的副本。它保存了默认的 Portlet
/Web 部件
信息,包括它们在门户上的位置,并在 ASP.NET 会话启动事件时填充。所有的定制和个性化都将在该实体中完成。
生成门户视图
它通过 ASP.NET 用户控件包含多个抽象层。每个用户控件都为实现整体视图贡献其特定部分。
门户 (Portal.aspx)
它是门户的入口点,并启动第一层抽象,它渲染局部视图 TabPage.asmx。
选项卡页面 (TabPage.ascx)
这是第一层抽象。它为各个类别生成选项卡页,然后将 portlet 分类到各自的选项卡中。每个选项卡都构成列或 web 部件区域,此处选项卡提供了三个列,分别是 portletColumn1XX
(第一列或左侧区域)、portletColumn2XX
(第二列或中间区域)和 portletColumn3XX
(第三列或右侧区域)。其中 XX 将被上下文类别 ID 替换。它们决定了 portlet 应该驻留在哪个类别和哪个列中。因此,portlet 的放置决策是在这一层做出的。
<%
System.Data.DataRow[] rows = ((WebApplication.Models.ds)Application
["data"]).Category.Select();
System.Data.DataRow[] piRows = rows;
int total_Category_Protlets = 0;
string status_Filter = " Is_Active = " +
ViewData["is_Active_Portlets"].ToString();
int total_Portlets = ((WebApplication.Models.ds)
Session["data"]).Portlet_User.Select(status_Filter).Length;
string funct_Name = "";
string status = "";
if (Convert.ToBoolean(ViewData["is_Active_Portlets"]))
status = " Active ";
else
status = " Disable ";
%>
<%-- application pagetabs are generation --%>
<div id="tabs" style="MIN-HEIGHT: 500px; HEIGHT: 100%">
<strong style="COLOR: black">Total : </strong><em id="currentActivePortlets"
style="COLOR: black">
<%= total_Portlets.ToString()%></em> <strong style="COLOR: black">
<%= status %> RSS Feeds</strong>
<ul>
<% foreach (System.Data.DataRow row in rows)
{ %>
<% total_Category_Protlets =
((WebApplication.Models.ds)Session["data"]).Portlet_User.Select
( status_Filter + " and Category_ID = " +
row["Category_ID"].ToString()).Length; %>
<li><a id="<%= "tab" + row["Category_ID"].ToString() %>"
href="%3C%=%20%22#tabs-%22%20+%20row[%22Category_ID%22].ToString%28%29%20%%3E">
<%= row["Category"].ToString() + "
( " + total_Category_Protlets.ToString() + " ) "%></a>
<%} %>
</li>
</ul>
<%-- application pagetabs contents generation that is portlets/webpart --%>
<% foreach (System.Data.DataRow catRow in rows)
{ %>
<% funct_Name = "catRadButton" + catRow["Category_ID"].ToString(); %>
<script type="text/javascript">
function <%= funct_Name %>() {
if( $('<%= "#radio1-" + catRow["Category_ID"].ToString() %>').
is(':checked') )
{
$('<%= "#" + "tabs-" + catRow
["Category_ID"].ToString() %>' ).find(".portlet").find
(".portlet-content").toggle(true);
$('<%= "#" + "tabs-" + catRow
["Category_ID"].ToString() %>' ).find(".portlet-header
.ui-icon-plusthick").toggleClass("ui-icon-minusthick").
toggleClass("ui-icon-plusthick");
}
else
if( $('<%= "#radio2-" + catRow["Category_ID"].ToString() %>').
is(':checked') )
{
$('<%= "#" + "tabs-" + catRow["Category_ID"].ToString() %>' ).
find(".portlet").find(".portlet-content").toggle(false);
$('<%= "#" + "tabs-" + catRow["Category_ID"].ToString() %>' ).
find(".portlet-header .ui-icon-minusthick").toggleClass
("ui-icon-minusthick").toggleClass("ui-icon-plusthick");
}
else
if( $('<%= "#radio3-" + catRow["Category_ID"].ToString() %>').is
(':checked') )
{
}
}
</script>
<div id="<%= "tabs-" + catRow["Category_ID"].ToString() %>" style="WIDTH: 100px">
<%-- intializing tabpage first column --%>
<%-- intializing tabpage second column --%>
<%-- intializing tabpage third column --%>
<table width="300">
<tbody>
<tr>
<td colspan="3"><input önclick="<%=" funct_name="" %="" type="radio">()
id="<%= "radio1-" + catRow["Category_ID"].ToString() %>"
name='<%= "cat-" + catRow["Category_ID"].ToString() %>' />
<label for="<%= "#radio1-" + catRow["Category_ID"].ToString() %>"
style="color: rgb(92, 135, 178);">Expand</label>
<input önclick="<%=" funct_name="" %="" type="radio">()
id="<%= "radio2-" + catRow["Category_ID"].ToString() %>"
name='<%= "cat-" + catRow["Category_ID"].ToString() %>' />
<label for="<%= "#radio2-" + catRow["Category_ID"].ToString() %>"
style="color: rgb(92, 135, 178);">Collapse</label>
<input önclick="<%=" funct_name="" %="" type="radio">()
id="<%= "radio3-" + catRow["Category_ID"].ToString() %>"
name='<%= "cat-" + catRow["Category_ID"].ToString() %>' checked=checked />
<label for="<%= "#radio3-" + catRow["Category_ID"].ToString() %>" s
tyle="color: rgb(92, 135, 178);">None</label> </td>
</tr>
<tr>
<td valign="top" style="WIDTH: 100px; VERTICAL-ALIGN: 100%">
<%-- intializing value for portlet/webpart that would passed to partial view as
parameter for further assesment --%> <% ViewDataDictionary vdd = new ViewDataDictionary();
vdd["category_ID"] = catRow["Category_ID"].ToString(); %>
<%-- first columns pagetabs portlets/webpart generation --%>s</td>
<td valign="top" style="WIDTH: 150px">
<%-- second columns pagetabs portlets/webpart generation --%>
<div class="column" id="<%= "portletColumn2" + catRow["Category_ID"].ToString() %>"
style="WIDTH: 285px; FONT-SIZE: 1.2em">
<% piRows = ((WebApplication.Models.ds)Session["data"]).Portlet_User.Select
(status_Filter + " and Category_ID = " + catRow["Category_ID"].ToString() +
" and Column_No = 2 ", " Row_Sequence asc ");
foreach (System.Data.DataRow piRow in piRows)
{ vdd["Portlet_ID"] = Convert.ToInt32(piRow["Portlet_ID"]);
vdd["Title"] = piRow["Title"].ToString(); Html.RenderPartial("Portlet", vdd); } %> </div>
</td>
<td valign="top" style="WIDTH: 150px">
<%-- second columns pagetabs portlets/webpart generation --%>
<div class="column" id="<%= "portletColumn3" +
catRow["Category_ID"].ToString() %>" style="WIDTH: 285px; FONT-SIZE: 1.2em">
<% piRows = ((WebApplication.Models.ds)Session["data"]).Portlet_User.Select
(status_Filter + " and Category_ID = " + catRow["Category_ID"].ToString() +
" and Column_No = 3 ", " Row_Sequence asc ");
foreach (System.Data.DataRow piRow in piRows)
{ vdd["Portlet_ID"] = Convert.ToInt32(piRow["Portlet_ID"]);
vdd["Title"] = piRow["Title"].ToString(); Html.RenderPartial("Portlet", vdd); } %> </div>
</td>
</tr>
</tbody>
</table>
</div>
<%} %>
</div>
Portlet (Portlet.ascx)
它是第二层抽象,仅从 TabPage.ascx 控件渲染,它定义了 Portlet/Web 部件框架,其内容需要驻留在此框架中。它依赖于下一层抽象,并依赖于对 portlet 控制器的内容控制器方法进行 AJAX 调用,该方法会将内容刷新到 portlet 框架中。
<%
string portlet_ID = Convert.ToInt32(ViewData["portlet_ID"]).ToString().Trim();
string portletName = "portlet" + portlet_ID;
string portletFunc = "func" + portletName;
string portletContent = "portlet" + portlet_ID + "Content";
%>
<script type="text/javascript">
function <%= portletFunc + "_" %>() {
<%= portletFunc %>("1");
}
function <%= portletFunc %>(page) {
if( page == 1 )
{
$('#<%= portletContent %>').html('');
}
else
{
$('#<%= portletContent %>').html
('<img alt="Loading, please wait"
src="https://codeproject.org.cn/ajax-loader.gif" />');
}
jQuery.ajax({
type:"POST",
url:"Portlet/Content/<%= portlet_ID %>/" + page +"/<%= portletName %>",
success: function(result) {
if(result.isOk == false)
{
$("#<%= portletContent %>").html(result.message);
$("#<%= "Header" + portletName %>").html("");
}
else
{
$("#<%= portletContent %>").html(result);
$("#<%= "Header" + portletName %>").html
("<%= ViewData["Title"] %>");
$(function() {
$("button, input:button, a", ".demo").button();
});
}
},
async: true
});
}
</script>
<%-- portlets/webpart generation --%>
<div class="portlet ui-state-default" id="<%= portletName %>" style="HEIGHT: 2%">
<div class="portlet-header">
<div id="<%= "Header" + portletName %>" style="COLOR: white">Loading..... </div>
</div>
<%-- portlets/webpart content holder--%>
<table width="100%">
<tbody>
<tr>
<td>
<div class="portlet-content" id="<%= portletContent %>">
<img alt="Loading, please wait" src=https://codeproject.org.cn/ajax-loader.gif
önload="<%=" />() /> </div>
</td>
</tr>
</tbody>
</table>
</div>
内容控制器方法
它是 Portlet 控制器的控制器方法,它接受 portlet_ID, page_No
和 portletName
。Portlet 控件使用 AJAX 调用它。portlet_ID
参数用于指定内容源,page_No
用于根据提供的参数缩减内容列表视图。
public ActionResult Content(int? portlet_ID, int page, string portletName)
{
// invalid portlet id
if( portlet_ID == null )
return View("ErrorPortalItem");
#region declaration
XmlNodeList objNL;
StringBuilder str = new StringBuilder();
// portlet page size
int pageSize = Convert.ToInt32
(System.Configuration.ConfigurationManager.AppSettings
["PageSize"].ToString());
string title = "";
string ItemLink = "";
string ItemTitle = "";
int total_Items = 0;
int last_Item_No = 0;
#endregion
#region loading RSS feed
string link = ((WebApplication.Models.ds)
this.HttpContext.Application["data"]).Portlet.FindByPortlet_ID
( Convert.ToInt32( portlet_ID ) ).Link;
XmlDocument objDoc = new XmlDocument();
try
{
objDoc.Load(link);
}
catch
{
// invalid portlet id
return View("ErrorPortalItem");
}
#endregion
#region parsing RSS parent node
objNL = objDoc.SelectNodes("rss/channel");
if (null != objNL)
{
// get title for portlet
title = objNL[0].ChildNodes[0].InnerText;
}
#endregion
#region parsing items in RSS feed
if (null != objDoc)
{
objNL = objDoc.SelectNodes("rss/channel/item");
if (null != objNL)
{
int counter = 1;
string description = "";
total_Items = objNL.Count;
foreach (XmlNode XNode in objNL)
{
if (counter >= ((page * pageSize) - pageSize) &&
counter <= (page * pageSize))
{
str.Append("<li>");
ItemTitle = "";
description = "";
foreach (XmlNode XNodeNested in XNode.ChildNodes)
{
switch (XNodeNested.Name)
{
case "description":
description = XNodeNested.InnerText;
break;
case "title":
ItemTitle = XNodeNested.InnerText;
break;
case "link":
ItemLink = XNodeNested.InnerText;
break;
}
}
str.Append("</a></li><a>");
last_Item_No = counter;
}
counter++;
}
}
}
#endregion
#region setting view naviation variables
double temp = total_Items / pageSize;
int possible_Pages = Convert.ToInt32(Math.Ceiling(temp));
ViewData["Next_Page"] = page + 1;
ViewData["Previous_Page"] = page - 1;
if ( ( page + 1 ) > possible_Pages)
ViewData["Is_Next_Page_Possible"] = false;
else
ViewData["Is_Next_Page_Possible"] = true;
if ( ( page ) == 1)
ViewData["Is_Previous_Page_Possible"] = false;
else
ViewData["Is_Previous_Page_Possible"] = true;
#endregion
#region setting views content variables
ViewData["content"] = str.ToString();
ViewData["title"] = title;
ViewData["portletName"] = portletName;
#endregion
return View();
}
详细视图
当内容列表项的描述过大或包含 HTML 文档时,需要将其显示在对话框中。提供了“详细信息”按钮来查看详细信息,该按钮将调用 JavaScript OpenDialog
函数。
<%
string funcItem = "func" + ViewData["portletName"].ToString();
string previous_Function = funcItem +
"(" + ViewData["Previous_Page"].ToString() + ")";
string next_Function = funcItem + "(" + ViewData["Next_Page"].ToString() + ")";
%>
<table width="100%">
<tbody>
<tr>
<td><%= ViewData["Header"].ToString() %> <%-- RSS feed content holder --%>
<%= ViewData["content"].ToString()%> </td>
</tr>
<tr>
<td><%-- content navigation--%> <%--
<div class="demo">--%> <%--
<p id="<%= "PortletContentLoading" + ViewData["item"].ToString() %>">
<img style="width:640px; height:480px" alt="Loading, please wait"
src="https://codeproject.org.cn/ajax-loader.gif" /> --%>
<% if (Convert.ToBoolean(ViewData["Is_Previous_Page_Possible"])) {%>
<input style="padding: 0em;" class="demo ui-button ui-widget ui-state-default
ui-corner-all"
value=" < " önclick="<%=" previous_function="" %="" type="button"> />
<%}%> <% if (Convert.ToBoolean(ViewData["Is_Next_Page_Possible"])) {%>
<input style="padding: 0em;" class="demo ui-button ui-widget ui-state-default
ui-corner-all" value=" >
" önclick="<%=" next_function="" %="" type="button"> /> <%}%> <%-- </p>
</div>
--%> </td>
</tr>
</tbody>
</table>
在调用该方法时,会向 Portlet 控制器的 GetItemDetail
控制器方法生成 AJAX 请求。它使用 portlet_ID
和 item_No
来获取特定 portlet 的特定项目的描述。该方法将返回要在对话框中显示的项目所需描述。
public ActionResult GetItemDetail(int? portlet_ID, int? item_No)
{
// invalid portlet id
if (portlet_ID == null || item_No == null)
return View("ErrorPortalItem");
#region declaration
XmlNodeList objNL;
StringBuilder str = new StringBuilder();
// portlet page size
int pageSize = Convert.ToInt32
(System.Configuration.ConfigurationManager.AppSettings
["PageSize"].ToString());
string title = "";
string imagePath = "";
string ItemName = "Item" + portlet_ID.ToString();
string ItemLink = "";
string ItemTitle = "";
#endregion
#region loading RSS path
string link = ((WebApplication.Models.ds)
this.HttpContext.Application["data"]).Portlet.FindByPortlet_ID
(Convert.ToInt32(portlet_ID)).Link;
XmlDocument objDoc = new XmlDocument();
try
{
objDoc.Load(link);
}
catch
{
// invalid portlet id
return View("ErrorPortalItem");
}
#endregion
#region parsing RSS header for title
objNL = objDoc.SelectNodes("rss/channel");
if (null != objNL)
{
// get title for portlet
title = objNL[0].ChildNodes[0].InnerText;
}
#endregion
#region get image path for portlet from RSS feed
objNL = objDoc.SelectNodes("rss/channel/image");
if (null != objNL)
{
objNL = objDoc.SelectNodes("rss/channel/image/url");
if (objNL.Count != 0)
imagePath = objNL[0].InnerText;
else
imagePath = "";
}
#endregion
#region content to be generated from RSS feed
int total_Items = 0;
if (null != objDoc)
{
objNL = objDoc.SelectNodes("rss/channel/item");
if (null != objNL)
{
int counter = 1;
string description = "";
total_Items = objNL.Count;
foreach (XmlNode XNode in objNL)
{
if (counter == item_No)
{
ItemTitle = "";
description = "";
foreach (XmlNode XNodeNested in XNode.ChildNodes)
{
switch (XNodeNested.Name)
{
case "description":
description = XNodeNested.InnerText;
break;
case "title":
ItemTitle = XNodeNested.InnerText;
break;
case "link":
ItemLink = XNodeNested.InnerText;
break;
}
}
break;
}
counter++;
}
str.Append(description);
}
}
#endregion
#region setting view content variables
ViewData["detail"] = str.ToString();
ViewData["imagePath"] = imagePath;
ViewData["title"] = title;
#endregion
return View("ItemDetail");
}
通过拖放进行定制
拖放个性化是通过 jQuery 库实现的,它提供了 portlet 放置和类别更改功能。
可拖放功能
当用户想要更改 Portlet/Web 部件类别时,他需要将特定的 Portlet/Web 部件拖放到该特定类别的选项卡中。为了实现此功能,解决方案中引入了 jquery.ui.draggable.js。当 Portlet/Web 部件被拖放到选项卡上时,会调用相关的 droppable 方法,同时调用 PortletsPlacementManager
方法,该方法在存储库中执行放置结算。
var current_Column_ID = "";
var portlet_Header_Icon;
var portlet_Item_Name = "";
$(function() {
var $tabs = $("#tabs").tabs();
var $tab_items = $("ul:first li",$tabs).droppable({
accept: '.column div' ,
tolerance: 'pointer',
opacity: .50,
containment: '.container',
cursor: 'move',
cursorAt: { cursor: 'move', top: -155, left: -55, bottom: 0 },
drop: function(ev, ui) {
var $item = $(this);
var $list = $($item.find('a').attr
('href')).find('.column')[0];
PortletsPlacementManager($($item.find('a').attr('href')).find
('.column').attr("id"),current_Column_ID,portlet_Item_Name,0,1);
ui.draggable.hide('slow', function() {
$tabs.tabs('select', $tab_items.index
($item));
$(this).appendTo($list).show('slow');
});
$(this).find(".portlet-content").toggle(true);
}
});
});
位置管理
每当 Portlet/Web 部件定制发生时,都会调用 PortletsPlacementManager
JavaScript 方法以将 Portlet 的放置调整与存储库同步。
function PortletsPlacementManager(column_ID,sender_Column_ID,
portlet_ID,row_No,is_Drop) {
jQuery.ajax({
type:"POST",
url:"Portlet/PortletsPlacementManager/" + column_ID + "/" +
portlet_ID +"/" + row_No + "/" + is_Drop ,
success: function(result) {
if(result.isOk != false)
{
if( is_Drop = 1 )
{
TotalCatetogoryPortlets(column_ID);
TotalCatetogoryPortlets(sender_Column_ID);
}
}
},
async: true
});
}
计算当前类别 Portlet
每当 Portlet/Web 部件类别更改时,特定类别中的 Portlet 也会随之更改。为了使视图与存储库同步,Portlet 从中转移的类别和需要重新评估其 Portlet 数量的转移目的地都需要重新评估。为此,调用了 TotalCatetogoryPortlets
方法,它接受列 ID(其中包含类别 ID)并返回分配给该类别的当前 Portlet 总数。
function TotalCatetogoryPortlets(column_ID) {
jQuery.ajax({
type:"POST",
url:"Portlet/TotalCatetogoryPortlets/" + column_ID,
success: function(result) {
if(result.isOk != false)
{
var name = "#tab" + column_ID.substring(4);
$(name).html( result );
}
},
async: true
});
}
管理 Portlet 状态
当用户想要从视图中移除或禁用 Portlet/Web 部件时,他只需取消该特定的 Portlet/Web 部件。取消后,该 Portlet/Web 部件将处于禁用状态。要重新激活它,用户需要转到“禁用 Portlet 列表”并单击标题顶部的“恢复”按钮。