VideoImage.jpg
   Portal Video

Table of Contents

Introduction

As an MVC Framework admirer, i had gone through high and low for Portlet or Wepart solution in ASP.NET MVC, but search didn't come up with desirable solution. This development effort is made to materialize Portlet/Webpart application in ASP.NET MVC Framework. It's indented to provide accumulated view with segregation, and offer's personalization features. It's intended to be concise as possible to focus most on idea, therefore ASP.NET session and application variable shall be our repository, therefore customed or personalized Portlet/Webpart adjustment shall not withstand for multiple ASP.NET Session.

Pre-requisites  

In order to follow this article, you need to have some understanding of MVC Framework. If you think you have sufficient expertise, then you are best to further read through this article.

If you still have not setup ASP.NET MVC, please have the items listed below installed before proceeding any further. You can also install ASP.NET MVC using Microsoft Web Platform Installer too.

Data Model

First to begin with, understanding data model is pivotal. It's entity describes the way the Portlet/Webpart would harmonized into Portal.

Data_Model.JPG
Category Entity

It hold's categories data, to segregate Portlet/Webpart into categories. Entity has one-many relationship with Portlet and Portlet_User entities. Entity would be habitated with data on start application event and resides in ASP.NET application variable.

Portlet Entity
It is child of Category entity, has one-many relationship with Portlet_User entity. It hold's default Portlet/Webpart information, regarding there placement on portal.
Portlet_ID : It's an entity's primary key, used to specify portlet.
Category_ID : It's hold category ID which demonstrate which category does Portlet/Webpart belongs
Link : It hold link to RSS Feed
Column_No : Describes column no. or webpart zone for the Portlet/Webpart.
Title : Holds RSS Feed title
Row Sequence : Holds row No. in particular column

Portlet User Entity
It's a child of Category, Portlet and User entity. it's more or less replica of Portlet entity. It hold's default Portlet/Webpart information, regarding there placement on portal, and habitated at ASP.NET Session start up event. All customization and personalization would be done in this Entity.

Generate Portal View

It comprises several level of abstraction through ASP.NET usercontrols. Each userControl performs it's specific contribution to have overall view.

Portal_Loading.jpg

Portal_Loaded.jpg

collapse.jpg

Portal ( Portal.aspx )

It's portal's entry point, and initiates first level of abstraction, it render's partial view TabPage.asmx.

Tab Page ( TabPage.ascx
It's a first level abstraction. It generate's tabpages for individual category which then segregates portlets to there repective tab. Each Tab constitues column or webpart zone, here Tab has come up with three column which are portletColumn1XX first column or left zone,portletColumn2XX for second column or middle zone and portletColumn3XX for third column or right zone. Where XX would be substituted with context category ID. they decides which category the portlet should be reside and in which column. Hence portlet placement decision made at this level.
      
  <% 
        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 : [__em id="currentActivePortlets" style="color: black; "__]<%= total_Portlets.ToString()%>  <%= status %> RSS Feeds
    <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="300px">  
            <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() %>" style="color: rgb(92, 135, 178);">None</label>
                </td>
            </tr>
            
            <tr><td style="width: 100px; vertical-align: 100%; " valign="top">
                    <%-- 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 coulumns pagetabs portlets/webpart generation  --%>s</td><td style="width: 150px; " valign="top">
                    <%-- second coulumns pagetabs portlets/webpart generation  --%>
                    <div id="<%= "portletColumn2" + catRow["Category_ID"].ToString() %>" class="column" style="margin-removed 0pt; margin-removed 0px; margin-removed 0px; margin-removed 0px; padding-removed 5px; padding-removed 5px; padding-removed 5px; padding-removed 5px; font-size: 1.2em; width: 285px; ">
		                <% 
                            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 style="width: 150px; " valign="top">
                    <%-- second coulumns pagetabs portlets/webpart generation  --%>
                    <div id="<%= "portletColumn3" + catRow["Category_ID"].ToString() %>" class="column" style="margin-removed 0pt; margin-removed 0px; margin-removed 0px; margin-removed 0px; padding-removed 5px; padding-removed 5px; padding-removed 5px; padding-removed 5px; font-size: 1.2em; width: 285px; ">
		                <% 
                            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 )

It's a second level abstraction and rendered only from TabPage.ascx control, it defines Portlet/Webpart frame in which it's content needs to be reside. It depend on next level abstraction, and relies on AJAX call to portlet controller's content controller method which will flush content inside portlet frame.

 
     <% 

    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 src="../../ajax-loader.gif" alt="Loading, please wait" />');
        }
        
        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" style="height: 2%; " id="<%= portletName %>">
            <div class="portlet-header" style="padding-removed 0pt; padding-removed 0pt; padding-removed 0pt; padding-removed 0pt; ">
                <div id="<%= "Header" + portletName %>" style="color: white; ">Loading..... </div>
            </div> 
            <%-- portlets/webpart content holder--%>
                    <table width="100%">
                <tbody><tr>
                    <td>
                        <div id="<%= portletContent %>" class="portlet-content">
                            <img src="../../ajax-loader.gif" alt="Loading, please wait" önload="<%=" />() />
                        </div>
                    </td>
                 </tr>
            </tbody></table>
    </div>
Content Controller Method

It's Portlet controller's controller method, it accepts portlet_ID, page_No and portletName. Portlet control invoke it using AJAX call. portlet_ID parameter is used to specify content source and page_No to slash down content list view accordance with provided parameters.   

 
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();
}

Detail View

When content list item's description is outsize it constraint size or it contain html document, then it need to be display in Dialog. Detail button is provided to view detail which would invoke javascript OpenDialog function.

 
       <% 
        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 src="../../ajax-loader.gif" alt="Loading, please wait" /> 
                    
--%>
                    <% 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>       

On calling the method get AJAX request is generated to Portlet controller's GetItemDetail controller method. It consumes portlet_ID and item_No to get specific portlet's specific item's description. Method will return required description of an Item to be displayed in dialog.

 
       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");
        }

Customization through Drag and Drop

Drag and drop personalization is accomplished through JQuery Library, which provides portlet placement and categroy change feature.

 

Drag_With_In_Tab.jpg

TabDrop.jpg

Droppable functionality

When uses want to change Portlet/Webpart category he needs to drag and drop particular Portlet/Webpart to that specific category tab. To implement this functionality jquery.ui.draggable.js is inducted in the solution. When Portlet/Webpart is draged and drop onto tab associated droppable method is called, during that mean time PortletsPlacementManager method is called which perform placement settlement in repository.

 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);
    			    
			    }
		    });
	    });
Placement Management 
Whenever Portlet/Webpart customization took place PortletsPlacementManager javascript method would be called to synchronize portlet's placement adjustment with repository.
 
       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
        }); 
        }
Calculate Current Category Portlet
Whenever portlet/Webpart category change, portlet in specific category changes as well, to have synchronize view with repository, category from where portlet is trasfer and where it trasfer required reevaluation it's portlets count, for that purpose TotalCatetogoryPortlets method is called, it take column id ( category id is resided within it) and return current total No. of portlets assigned to that category.
	
  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
        }); 
        }	

Managing Portlets Status

When uses want to remove or disable Portlet/Webpart from his view he just need to cancel that particular Portlet/Webpart. By canceling it that Portlet/Webpart shall have disable status. To reactivate it user need to go to Disabled Portlet list and just click restore button at top of header.

Disabled_Portlets.jpg

推荐.NET配套的通用数据层ORM框架:CYQ.Data 通用数据层框架
新浪微博粉丝精灵,刷粉丝、刷评论、刷转发、企业商家微博营销必备工具"