如何把ASP.NET網頁部署至AZURE

Azure簡介

微軟的雲端作業系統。登入https://manage.windowsazure.com/或進到Azure官網點右上角的Portal。

目前Azure支援的有Web Sites, Cloud Services, SQL Databases, Storage。

Web Sites : 靜態網頁。只態使用IIS。

Cloud Services : 動態內容。即Web Application, Web Service等。

SQL Database : 資料庫。

Storage : 類似Google Big Table,好像還可以儲存檔案。

ASP.NET web application

會用到SQL Database和Cloud Service。

SQL DATABASES

首先建立一個Database,Collation目前使用SQL_Latin1_General_CP1_CI_AS。透過SQL的[N””]可儲存中文,例如INSERT INTO table (column) VALUES (N’中文內容’)。其他參數跟付費內容有關。

建立後系統會產生一個domain,例如我們在東亞的abc1234def.database.windows.net,這個可以用ssms登入管理。頁面上有一個[Show connection strings]可以顯示連結字串,預設限定tcp,不能使用namepipe,而且有firewall,要在firewall加入允許規則才可以連線。防火牆設定在SQL Databases->Servers Tab->選一個instance->Configure

SQL schema請使用Sql Migration Wizard從Local Database上傳至Azure Sql Database

CLOUD SERVICES

先建立一個instance,quick create就可以,有兩種模式,production和staging,測試中就選用staging。接下來要先把Web Application打包,deploy後才能再作後續設定。

VS2010

下載並安裝Azure SDK。在原本的Solution底下新增一個Azure Project,不要選擇任何web role->完成。[以下稱Azure Project]

產生空白專案後,在Solution Explorer->Roles上按右鍵,選Web Role in solution,把你想deploy到Azure的web application project加進去[以下稱WebApp]。在WebApp加入Microsoft.WindowsAzure.ServiceRuntime這個參考。

接下來要調整web.config。可以透過在Azure Project按右鍵->屬性設定,也可以在cscfg和csdef這兩個xml手動設定。Csdef會定義cdcfg裡有甚麼設定值(session),實際運行用的參數值會在cscfg。大概只會用到connectinstring。Connectionstring請參考在SQL DATABASE一節的取得方法。

修改Source Code

Azure不支援ConfigurationManager物件,請把所有跟ConfigurationManager相關的程式碼修改成如下(以connectionstring為例):

if (RoleEnvironment.IsAvailable)

{

param = RoleEnvironment.GetConfigurationSettingValue(“key”);

}

else

{

param = ConfigurationManager.ConnectionStrings[“key”].ConnectionString;

}

封裝Azure Project

在azure project按右鍵,選封裝,系統就會把專案包裝成兩個檔案,準備好上傳至Azure Cloud Service。這時候請注意VS2010的輸出,如出現WAT##等警告,請執行對應的處理。

X64平台

Cloud Service是X64平台,所以你的WebApp及所有參考的dll都需要編譯成x64使用。

服務部署

在Azure Portal選Cloud Services,選一個instance,再選Deploy,輸入Deployment Name,package是封裝時產生的.cspkg檔,configuration是.cscfg檔。完成後請選Link Resources頁面,新增SQL Database一節時所建立的 Instance。到這裡基本上已完成WebApp的部署

Appendix

Membership

如果你的程式有使用Asp.Net Memebership,請使用以下script在SQL Database的管理介面執行,這些script會建立所需要的tables, views, store procedures。

注意,必需先執行InstallCommon.sql。

網頁亂碼

請檢查WebApp的source code,需使用UTF-8儲存,檢查及修改在VS2010->檔案->進階儲存選項…

Google Map + ASP.NET Series 1

I have made a little web page with google map api. Here are issues I dealed with.
Some of them are of ASP.NET, some of them are of Google Map.

Here are a little features description of my application.
1. Move to one of the marker and zoom in.
2. Pop up info window with information.
3. Showing the mouse Lat. and Lng.
4. Draging marker.
5. Show/hide maker’s name label.
6. Show/hide markers by category.
7. Show information of each station with label.
1-4 are features that provide by Google api.
5-7 are features that needs to work on.

1-3
4

The first thing you need to deal with will be ‘s onload and onunload event.
Google’s simple guide is adding an initialize() to onload and adding GUnload() to onunload.
It is not a good idea if you have your on master page and
make it runat=”server”, putting javascript functions to it.

I get around like this. A div with Id=”GMap” and runat=”server”.
In code-behind:
Page.ClientScript.RegisterStartupScript(this.GetType(), "OnLoad", string.Format("AddOnLoadFunctionParam(initialize,\"{0}\");", GMap.ClientID), true);
Page.ClientScript.RegisterStartupScript(this.GetType(), "OnUnload", "GUnload();", true);

In Javascript:
function AddOnLoadFunctionParam(func, containerId) {
$addHandler(window, "load", function() { func(containerId) });
return;
}
function AddOnUnloadFunction(func) {
$addHandler(window, "unload", func);
return;
}
function initialize(id) {
var map = new google.maps.Map2(document.getElementById(id));
map.setCenter(new google.maps.LatLng(37.4419, -122.1419), 13);
//do what you need to do.
}

I pass the div’s Id to the javascript by using the registering it to the page. In that javascript function.
I am using ASP.NET AJAX Client Library function (although nothing AJAX here), to append it as an onload event handler.
This will preserve all other onload event handler. Do the same to onunload with GUnload().

Try it, and write to me if you get any question. Since I have done this quite a while ago.
And write this by memory, and I know my memory is not that good.

Generate Statistic Chart with ZedGraph in ASP.NET

中文翻譯請稍候.
The chart generation work is handled by my former colleague Peter. But he left.
He did the job with his own little chart library which I don’t know how to use it.
So I look for an open source and/or free solution. I find ZedGraph and it looks pretty much 90% what I need.

After experiencing ZedGraph, it is very amazing how easy to use ZedGraph and its features are so complete!

Download ZedGraph first.
If you are not going to make your own version or dive into the source, just download dll only.

Create a Web Application, add both ZedGraph.dll and ZedGraph.Web.dll to the reference.
And then right click on the toolbox, add new server control by adding ZedGraph.Web.dll.
You can now drag the ZedGraph control to your design view.
Remember to *REMOVE* all other tags, just leave

and the ZedGraphWeb control behind.
Result in something like this :
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="GenerateLineChart.aspx.cs" Inherits="TaoYuan.HistoryData.ByItemGroup.GenerateLineChart" %>
<%@ Register Assembly="ZedGraph.Web" Namespace="ZedGraph.Web" TagPrefix="zgw" %>

As you can read in their document, ZedGraph works in two modes.
I am not going to explain it since their have nice document on that.
I will show how to use the stream mode to expose a Graph as a url.

Back to the page which we have a ZedGraph control.
We assign an event handler to RenderGraph event which will define how the graph is rendered. Assign like the following :
zedGraphControl.RenderGraph += new ZedGraph.Web.ZedGraphWebControlEventHandler(zedGraphControl_RenderGraph);
public void zedGraphControl_RenderGraph(ZedGraphWeb zgw, Graphics g, MasterPane masterPane){...}

Inside the handler method, you will be given a default master pane.
I maybe talk about master pane later, let’s just use one graph in this post.
What you need to do here is setup the graph style, bind data and done.
I will just show a demo snippet with a little explaination.

//get a graph pane
GraphPane masterPaneDefault = masterPane[0];
//style the title and axises
masterPaneDefault.Title.Text = m_selectedStation.Name;
masterPaneDefault.XAxis.Title.IsVisible = false;
masterPaneDefault.YAxis.Title.Text = m_selectedItemInfo.Name+'('+ m_selectedItemInfo.Unit +')';
//we are going to use line chart here
//use data to make a curve line
PointPairList theLine = new PointPairList();
foreach (DataRow dr in m_DataFound.Rows)
{
DateTime l_date = (DateTime)dr[0];
double x = (double)new XDate(l_date);
double y = 0;
if (!DBNull.Value.Equals(dr[1]))
{
y = (Single)dr[1];
theLine.Add(x, y);
}
}
//set angle of xaxis label
masterPaneDefault.XAxis.Scale.FontSpec.Angle = 45;
//add the line to our graph pane
LineItem theCurve = masterPaneDefault.AddCurve(m_selectedItemInfo.Name, theLine, Color.Red, SymbolType.XCross);
masterPaneDefault.XAxis.Type = AxisType.DateAsOrdinal;
masterPaneDefault.XAxis.Scale.Format = "yyyy/MM/dd HH:mm";
//smooth the curve
//theCurve.Line.IsSmooth = true;
// Make curves thicker
//theCurve.Line.Width = 3.0F;
// Add gridlines to the plot, and make them gray
masterPaneDefault.XAxis.MajorGrid.IsVisible = true;
masterPaneDefault.YAxis.MajorGrid.IsVisible = true;
masterPaneDefault.XAxis.MajorGrid.Color = Color.Gray;
masterPaneDefault.YAxis.MajorGrid.Color = Color.LightGray;
masterPaneDefault.XAxis.MinorGrid.IsVisible = true;
masterPaneDefault.XAxis.MinorGrid.Color = Color.Khaki;
// Move the legend location
masterPaneDefault.Legend.Position = LegendPos.Right;
// Add a background gradient fill to the axis frame
masterPaneDefault.Chart.Fill = new Fill(Color.White, Color.FromArgb(255, 255, 210), -45F);
masterPaneDefault.Fill = new Fill(Color.FromArgb(250, 250, 255));
//masterPaneDefault.XAxis.Scale.MajorStep = 2.5d;
masterPaneDefault.Legend.IsVisible = false;
//calculate the axis scale ranges
masterPaneDefault.AxisChange();

The result will look something like this.
line chart
That’s it. There are other great charting tools avaiable.
The MS Chart control and Flot are both great. Check them out if you like.

Remembering ListView page index and restoring it.

I am using ListView with DataPager to get a paging feature of my list.
I want to redirect my page when I click on an entry in the list view, show some detail information.
When I am done, I want to go back and view the exact same list of information.
That is, remembering the page index.

我是用DataPager幫我的ListView做分頁功能.我想要在使用者點選列表上某一條目後,
轉跳到別的頁面,顯示一些詳細資訊後,完成後回到剛才的列表頁面,要能看到剛才的那一個分頁.
就是說,要記住之前的頁碼.

Inside your ItemCommand handler method, use the following code snippet to find the correct startRowIndex
在你的ItemCommand處理程序裡面,用下面的程式片段去找出正確的startRowIndex

//index of the clicked item within the whole list
int l_dataIndex = ((ListViewDataItem)e.Item).DataItemIndex;
//minus the index of the item in that page = startRowIndex
int l_startRowIndex = l_dataIndex - ((ListViewDataItem)e.Item).DisplayIndex;
Store this index whatever you want, I store it by using GET method.
把索引值存在你喜歡的地方,我把它儲存在GET方法裡

Why do I call the above correct startRowIndex and not using the DataPager.startRowIndex?
Because we are going to use DataPager.SetPageProperties() in Page_Load() to restore the page we previously looking at.
為甚麼我會說上面的是正確的startRowIndex還有不去用DataPager.startRowIndex呢?
因為我們將會在Page_Load()裡用DataPager.SetPageProperties()去顯示之前我們在看的分頁

dataPager.SetPageProperties(l_startRowIndex, dpg_dataPager.MaximumRows, true);

This will bring you back to the page as we expect. But after this call.
The startRowIndex won’t update now even though you click next/previous to change your page.
That is why I said this is not the correct startRowIndex. You can try this youself ^^.
這個調用會把你帶到之前的分頁,正如我們預期.但在這個調用後,
startRowIndex就會停止更新,就算你點選上一頁/下一頁也不會改變.
這就是為甚麼我說上面的才是正確的startRowIndex.你可以自己試一試 ^^.

It turns out that I have done something stupid. The SetPageProperties() have to be inside IsPostBack to work correctly.
Result in something like this.

//in ItemCommand handler
url += "&startRowIndex=" + dataPager.startRowIndex;

//in Page_Load
if (!IsPostBack)
{
dataPager.SetPageProperties(startRowIndex, dataPager.MaximumRows, true);
}

Keep it MVC. Use templated controls without injecting Eval() in your aspx.

I am quite stubborn in keeping .aspx clean, try to keep only asp and html tag in it.

Until recently I need to use ListView and bind my data to it, almost 90% of the example available are using <%# Eval(“[PropertyName]”) %>.

But I really don’t want to stuff my .aspx with these things, I don’t know if they are VBScript or ASP code, anyway, I want to do that in C# code.

I image that is possible to customize my binding just like I did in Java. After searching and reading articles for a few hours, I finally got it.

I want to share my experience here hoping to save someone a few hours. I will try to give sample code download if I can.

First step, drag a ListView control to your page. There are three things a ListView must have.

1.You must have a <LayoutTemplate> in your ListView.

2.You must have a <PlaceHolder> in your <LayoutTemplate>.

3.Set ItemPlaceholderId to your PlaceHolder Id.

I am using table to display my data. You will result in something that looks like this:

<asp:ListView ID="ltv_users" runat="server" ItemPlaceholderID="plh_userItems"
        onitemcommand="ltv_users_ItemCommand"
        ondatabinding="ltv_users_DataBinding" onitemdeleting="ltv_users_ItemDeleting">
        <LayoutTemplate>
            <table class="ListViewTableStyle" border="1">
                <thead class="ListViewHeaderStyle">
                    <tr>
                        <td>
                        </td>
                        <td>
                            Column Header 1
                        </td>
                        <td>
                            Column Header 2
                        </td>
                    </tr>
                </thead>
                <tbody>
                    <asp:PlaceHolder ID="plh_userItems" runat="server"></asp:PlaceHolder>
                </tbody>
            </table>
        </LayoutTemplate>
    </asp:ListView>

Second Step is to define your own customize Template to do data binding. Create a class which implement ITemplate interface under System.Web.UI namespace. People usually pass an enumerator to the constructor to indicate what type of template to create. The result looks like this.

public class UserTemplate : ITemplate
    {
        protected ListViewTemplateType m_templateType;
        public UserTemplate(ListViewTemplateType type)
        {
            m_templateType = type;
        }
    }

ListViewTemplateType is just an enumerator one to one maps to template available inside ListView tag. Then we implement two methods, InstantiateIn(Control container) which response for creating html tags and data binding method. Here is an example for generating different tags for different template.

public void InstantiateIn(Control container)
        {
            //for itemtemplate and alternativeitemtemplate
            LinkButton l_btnDetail = new LinkButton() { Text = m_DetailBtnText, ID = "btnDetail", CommandName = "Detail", CssClass = "ListViewButton" };
            LinkButton l_btnRole = new LinkButton() { Text = m_RoleBtnText, ID = "btnRole", CommandName = "Role", CssClass = "ListViewButton" };
            LinkButton l_btnDelete = new LinkButton() { Text = m_DeleteBtnText, ID = "btnDelete", CommandName = "Delete", CssClass = "ListViewButton" };
            Label l_lblAccount = new Label() { ID = "lblAccount" };
            Label l_lblUserName = new Label() { ID = "lblUserName" };
            switch (m_templateType)
            {
                //ListViewItemType.
                case ListViewTemplateType.ItemTemplate:
                    container.Controls.Add(new LiteralControl("<tr class=\"ListViewItemStyle\"><td>"));
                    container.Controls.Add(l_btnDetail);
                    container.Controls.Add(l_btnRole);
                    container.Controls.Add(l_btnDelete);
                    container.Controls.Add(new LiteralControl("</td><td>"));
                    container.Controls.Add(l_lblAccount);
                    container.Controls.Add(new LiteralControl("</td><td>"));
                    container.Controls.Add(l_lblUserName);
                    container.Controls.Add(new LiteralControl("</td></tr>"));
                    container.DataBinding += new EventHandler(ItemTemplate_DataBinding);
                    break;
                case ListViewTemplateType.AlternatingItemTemplate:
                    container.Controls.Add(new LiteralControl("<tr class=\"ListViewAlternativeItemStyle\"><td>"));
                    container.Controls.Add(l_btnDetail);
                    container.Controls.Add(l_btnRole);
                    container.Controls.Add(l_btnDelete);
                    container.Controls.Add(new LiteralControl("</td><td>"));
                    container.Controls.Add(l_lblAccount);
                    container.Controls.Add(new LiteralControl("</td><td>"));
                    container.Controls.Add(l_lblUserName);
                    container.Controls.Add(new LiteralControl("</td></tr>"));
                    container.DataBinding += new EventHandler(ItemTemplate_DataBinding);
                    break;
            }
        }

We need to define controls id so that we can later bind data to them by their id in ItemTemplate_DataBinding().

public void ItemTemplate_DataBinding(object sender, EventArgs e)
        {
            ListViewDataItem listViewDataItem = (ListViewDataItem)sender;
            string l_account = (string)DataBinder.Eval(listViewDataItem.DataItem, "Account");
            string l_userName = (string)DataBinder.Eval(listViewDataItem.DataItem, "FirstName") + (string)DataBinder.Eval(listViewDataItem.DataItem, "LastName");
            Guid l_userId = (Guid)DataBinder.Eval(listViewDataItem.DataItem, "Id");
            switch (m_templateType)
            {
                case ListViewTemplateType.ItemTemplate:
                case ListViewTemplateType.AlternatingItemTemplate:
                    ((LinkButton)listViewDataItem.FindControl("btnDetail")).CommandArgument = l_userId.ToString();
                    ((LinkButton)listViewDataItem.FindControl("btnRole")).CommandArgument = l_userId.ToString();
                    //((LinkButton)listViewDataItem.FindControl("btnDelete")).CommandArgument = l_userId.ToString();
                    LinkButton btnDelete = (LinkButton)listViewDataItem.FindControl("btnDelete");
                    btnDelete.CommandArgument = l_userId.ToString();
                    btnDelete.OnClientClick = m_ConfirmScript;
                    ((Label)listViewDataItem.FindControl("lblAccount")).Text = l_account;
                    ((Label)listViewDataItem.FindControl("lblUserName")).Text = l_userName;
                    break;
            }
        }

I am using a fall through here because ItemTemplate and AlternatingItemTemplate are binding data in the same way. You can bind textbox for ItemEditTemplate. I am going to show how to use this template class we make just now. How to work under asp page life cycle and use your own data source, and something about default ListView event handling. Read It Now!