65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.84/5 (38投票s)

2011 年 1 月 21 日

CPOL

5分钟阅读

viewsIcon

228263

downloadIcon

7363

在 ASP.NET MVC 框架中,一个解决方案通过拖放 Portlet/Web 部件定制功能进行增强。它利用 jQuery 提供了更好的用户体验,避免了在个性化过程中进行整体页面刷新。

webpartsMVC/VideoImage.jpg
门户视频

应用程序的实时演示

目录

引言

作为 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 部件如何与门户协调一致。

webpartsMVC/Data_Model.JPG

类别实体

它保存类别数据,用于将 Portlet/Web 部件划分到不同的类别中。实体与 PortletPortlet_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 用户实体

它是 CategoryPortletUser 实体的子实体。它或多或少是 Portlet 实体的副本。它保存了默认的 Portlet/Web 部件信息,包括它们在门户上的位置,并在 ASP.NET 会话启动事件时填充。所有的定制和个性化都将在该实体中完成。

生成门户视图

它通过 ASP.NET 用户控件包含多个抽象层。每个用户控件都为实现整体视图贡献其特定部分。

webpartsMVC/Portal_Loading.jpg

webpartsMVC/Portal_Loaded.jpg

webpartsMVC/collapse.jpg

门户 (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_NoportletName。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_IDitem_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 放置和类别更改功能。

webpartsMVC/Drag_With_In_Tab.jpg

webpartsMVC/TabDrop.jpg

可拖放功能

当用户想要更改 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 列表”并单击标题顶部的“恢复”按钮。

webpartsMVC/Disabled_Portlets.jpg

© . All rights reserved.