2013年11月14日 星期四

ASP.NET MVC 4 中 Controller 與 ApiController 做讀取、新增、更新、刪除 ( CRUD )

在 ASP.NET MVC 4 架構上,WebApi ( ApiController ) 較適合做資料處理與提供的動作,而 MVC 4 Web ( Controller ) 內比較要配合 View 層資料顯示而做異動,這時候如果要把 ApiController 和 Controller 切開來,那這兩個部分的溝通就會很常使用了。如果你是用 WebApi 與純 HTML + javascript 的架構,若是一頁蒐集各資料表的某些資料,那在 View 那一段就必須要與 WebApi 溝通多次才能將資料湊齊,再加上關聯資料,那就更複雜了,另外一點,如果各個 WebApi 的 Url 寫在頁面上,可能會有資安的危險,因為 WebApi 是開放的資料,如果不是任何人都能存取,那就不能玩此架構了。

在 Api 與 View 之間在卡一個 Controller 或者是 WebApi,做資料中繼站,把資料蒐集好在一次送到 View,而 View 收到資料也只需要拆解資料後分散到各個欄位之中。最後要 submit 整包送回 Model 也是一樣在 Controller 或者是 WebApi 解析驗證後再分散到各個 Api ,這樣資安會大大的提升。

因此此篇要講解如何在 Controller 對 WebApi 做讀取、新增、更新、刪除 ( CRUD ) 的動作。

1.

在 BaseController ( 如果沒有 BaseController 就寫在該隻 Controller 內 ) 繼承 ApiController 或 Controller,再來建置幾個變數:
public class BaseApiController : ApiController 
{

    protected DefaultConnection db = new DefaultConnection(); // 資料庫連接

    protected HttpClient client;

    protected HttpResponseMessage response;

    public BaseApiController()
    {
        client = new HttpClient();
        client.BaseAddress = new Uri("Api Url");
    }
}

2.

這時候在可以在 Controller 去非同步取得 WebApi 的結果,在宣告回傳數值前要加上 async,並且回傳數值要用 Task 包起來,說明這是個數值是非同步取得的結果,於接收值端前面要加上 await 前綴參考,表示暫停執行方法,直到等候的工作完成。所以在程式中要引用參考:
using System.Threading.Tasks;

在接著非同步取得回來的 Json 字串要轉成類別形式,需要用到 JsonConvert 的方法,所以要在 NuGet 下載 Json.NET 外掛:

程式中要再引用參考:
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

3.

在 Index 的 Function 中完整程式碼:
public async Task<ActionResult> Index()
{
    response = await client.GetAsync("api/RMS/CompanyApi/");
    string t_s = await response.Content.ReadAsStringAsync();
    var rms_company = JsonConvert.DeserializeObject<List<RMS_Company>>(t_s);

    return View(rms_company);
}

在 Details 的 Function 中完整程式碼:
public async Task<ActionResult> Details(Guid? id = null)
{
    response = await client.GetAsync("api/RMS/CompanyApi/" + id);
    string t_s = await response.Content.ReadAsStringAsync();
    var rms_company = JsonConvert.DeserializeObject<RMS_Company>(t_s);

    if (rms_company == null)
    {
        return HttpNotFound();
    }
    return View(rms_company);
}

在 Create 的 Function 中完整程式碼:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Create(RMS_Company rms_company)
{
    if (ModelState.IsValid)
    {
        setContent(JsonConvert.SerializeObject(rms_company));
        response = await client.PostAsync("api/RMS/CompanyApi", content);

        // db.RMS_Company.Add(rms_company);
        // db.SaveChanges();
        return RedirectToAction("Index");
    }

    return View(rms_company);
}

在 Edit 的 Function 中完整程式碼:
public async Task<ActionResult> Edit(Guid? id = null)
{
    // RMS_Company rms_company = db.RMS_Company.Find(id);
    response = await client.GetAsync("api/RMS/CompanyApi/" + id);
    string t_s = await response.Content.ReadAsStringAsync();
    var rms_company = JsonConvert.DeserializeObject<RMS_Company>(t_s);

    if (rms_company == null)
    {
        return HttpNotFound();
    }
    return View(rms_company);
}

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Edit(RMS_Company rms_company)
{
    if (ModelState.IsValid)
    {
        rms_company.Updater = Guid.NewGuid();
        rms_company.UpdateOn = DateTime.Now;

        setContent(JsonConvert.SerializeObject(rms_company));
        response = await client.PutAsync("api/RMS/CompanyApi/" + rms_company.CompanyId, content);

        // db.Entry(rms_company).State = EntityState.Modified;
        // db.SaveChanges();
        return RedirectToAction("Index");
    }
    return View(rms_company);
}

在 Delete 的 Function 中完整程式碼:
public async Task<ActionResult> Delete(Guid? id = null)
{
    // RMS_Company rms_company = db.RMS_Company.Find(id);
    response = await client.GetAsync("api/RMS/CompanyApi/" + id);
    string t_s = await response.Content.ReadAsStringAsync();
    var rms_company = JsonConvert.DeserializeObject<RMS_Company>(t_s);
    
    if (rms_company == null)
    {
        return HttpNotFound();
    }
    return View(rms_company);
}

[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public async Task<ActionResult> DeleteConfirmed(Guid id)
{
    // RMS_Company rms_company = db.RMS_Company.Find(id);
    response = await client.DeleteAsync("api/RMS/CompanyApi/" + id);
    // db.RMS_Company.Remove(rms_company);
    // db.SaveChanges();
    return RedirectToAction("Index");
}




2013年11月12日 星期二

Model 更動欄位時後出現「未套用自動移轉,因為這可能會造成資料遺失」

當我針對一個 Model 做欄位上的字數限制更動時


要將 CompanyCode 欄位字數最大限制 (MaxLength) 從 50 改為 40,然後重新執行網站測試,結果發現以下錯誤:


這大概是程式人員在寫 MVC 碰到會很頭大的問題吧! 因為我有做 Code First 自動轉移,所以後來去找了一下怎麼處理,有人是在主控台下 Update-Database -Script -Force 把 T-Sql 叫出來給使用者確認,但我叫出來是空的。

最後找到問題所在,就是要在 「Migration\configuration.cs」裡面 AutomaticMigrationsEnabled 下加上一個 AutomaticMigrationDataLossAllowed 為 true。看起來像這樣:
public Configuration()
{
    AutomaticMigrationsEnabled = true;
    AutomaticMigrationDataLossAllowed = true;
}

因為資料本身長度為 50,如果改為 40,則原本長度 40 以上的資料會遺失,因此轉移失敗,但加上 AutomaticMigrationDataLossAllowed 的意思就是說不管那些資料重不重要,反正林北就是要轉這是一組非常有魄力的指令。推薦給有魄力的人使用。


ASP.NET MVC 4 WebApi 的 BaseApiController 之 Repository 模式

在 ASP.NET MVC 4 WebApi 專案底下,常常在 Controller 需要寫許多很類似的程式碼,只是對象 Model 不一樣做些許的改變。

在此介紹如何在 Controller 之下建置底層,使 Controller 變得只需要宣告、繼承、實作即可與原本 Controller 能做的事情一樣:
namespace RMSApi.Controllers
{
    public class CompanyController : BaseApiController
    {
        // 這裡是空的,真的是這樣
    }
}

步驟是研究與參考 Generic Repository Pattern with Entity Framework and Web API ( Entity Framework 和 Web API 的通用 Repository 模式 ),目前唯一的限制就是你的資料庫 Key 值名稱都要一樣,我目前專案已經有了這項衝突,所以這個方法介紹給有緣人,建置過程必須依照以下方式:

1.

在 Model 內新增一個 Interface 資料夾,建立一個 interface 類別 IRepository 繼承 IDisposable,裡面宣告一些常用的方法:
using System;
using System.Data.SqlClient;
using System.Linq;
using System.Linq.Expressions;
namespace RMSApi.Models.Interface
{
    public interface IRepository: IDisposable 
    {
        /* 定義需要實作的函數 */


        IQueryable<T> All<T>(string[] includes = null) where T : class;

        T Get<T>(Expression<Func<T, bool>> expression, string[] includes = null) where T : class;

        T Find<T>(Expression<Func<T, bool>> predicate, string[] includes = null) where T : class;

        IQueryable<T> Filter<T>(Expression<Func<T, bool>> predicate, string[] includes = null) where T : class;

        IQueryable<T> Filter<T>(Expression<Func<T, bool>> filter, out int total, int index = 0, int size = 50, string[] includes = null) where T : class;

        bool Contains<T>(Expression<Func<T, bool>> predicate) where T : class;

        T Create<T>(T t) where T : class;

        int Delete<T>(T t) where T : class;

        int Delete<T>(Expression<Func<T, bool>> predicate) where T : class;

        int Update<T>(T t) where T : class;

        void SaveChanges();

        void ExecuteProcedure(String procedureCommand, params SqlParameter[] sqlParams);

    }
}

2.

接著建立每個 Model 內一定會出現的資料欄位的 Interface,視習慣建立兩個或者統整到同一個檔案中,像我這裡是 CreateOn 和 UpdateOn。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace RMSApi.Models.Interface
{
    public interface ICreateOn
    {
        DateTime CreateOn { get; set; }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace RMSApi.Models.Interface
{
    public interface IUpdateOn
    {
        DateTime UpdateOn { get; set; }
    }
}

再建立一個 IIdentifier,這就是你每個 Model 的 Key 值,需要一模一樣。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace RMSApi.Models.Interface
{
    public interface IIdentifier
    {
        Guid ID { get; set; }
    }
}


3.

在 Model 內新增一個 Repository 資料夾,建立一個類別 Repository 繼承 IRepository,裡面宣告一些常用的方法:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

using System.Data;
using System.Data.SqlClient;
using RMSApi.Models.Interface;
using RMSApi.Models;
using System.Linq.Expressions;
using System.Web.Http;

namespace RMSApi.Models.Repository
{
    public class Repository : IRepository
    {
        private DefaultConnection db;

        public Repository()
        {
            db = new DefaultConnection();

            // db.Configuration.ProxyCreationEnabled = false;

            // db.Configuration.LazyLoadingEnabled = false;
        }

        public IQueryable<T> All<T>(string[] includes = null) where T : class
        {
            if (includes != null && includes.Count() > 0)
            {
                var query = db.Set<T>().Include(includes.First());
                foreach (var include in includes.Skip(1))
                    query = query.Include(include);
                return query.AsQueryable();
            }

            return db.Set<T>().AsQueryable();
        }

        public T Get<T>(Expression<Func<T, bool>> expression, string[] includes = null) where T : class
        {
            return All<T>(includes).FirstOrDefault(expression);
        }

        public virtual T Find<T>(Expression<Func<T, bool>> predicate, string[] includes = null) where T : class
        {
            if (includes != null && includes.Count() > 0)
            {
                var query = db.Set<T>().Include(includes.First());
                foreach (var include in includes.Skip(1))
                    query = query.Include(include);
                return query.FirstOrDefault<T>(predicate);
            }

            return db.Set<T>().FirstOrDefault<T>(predicate);
        }

        public virtual IQueryable<T> Filter<T>(Expression<Func<T, bool>> predicate, string[] includes = null) where T : class
        {
            if (includes != null && includes.Count() > 0)
            {
                var query = db.Set<T>().Include(includes.First());
                foreach (var include in includes.Skip(1))
                    query = query.Include(include);
                return query.Where<T>(predicate).AsQueryable<T>();
            }

            return db.Set<T>().Where<T>(predicate).AsQueryable<T>();
        }

        public virtual IQueryable<T> Filter<T>(Expression<Func<T, bool>> predicate, out int total, int index = 0, int size = 50, string[] includes = null) where T : class
        {
            int skipCount = index * size;
            IQueryable<T> _resetSet;

            if (includes != null && includes.Count() > 0)
            {
                var query = db.Set<T>().Include(includes.First());
                foreach (var include in includes.Skip(1))
                    query = query.Include(include);
                _resetSet = predicate != null ? query.Where<T>(predicate).AsQueryable() : query.AsQueryable();
            }
            else
            {
                _resetSet = predicate != null ? db.Set<T>().Where<T>(predicate).AsQueryable() : db.Set<T>().AsQueryable();
            }

            _resetSet = skipCount == 0 ? _resetSet.Take(size) : _resetSet.Skip(skipCount).Take(size);
            total = _resetSet.Count();
            return _resetSet.AsQueryable();
        }

        public virtual T Create<T>(T TObject) where T : class
        {
            if (TObject is ICreateOn)
            {
                (TObject as ICreateOn).CreateOn = DateTime.Now;
            }

            if (TObject is IUpdateOn)
            {
                (TObject as IUpdateOn).UpdateOn = DateTime.Now;
            }

            var newEntry = db.Set<T>().Add(TObject);
            db.SaveChanges();
            return newEntry;
        }

        public virtual int Delete<T>(T TObject) where T : class
        {
            db.Set<T>().Remove(TObject);
            return db.SaveChanges();
        }

        public virtual int Update<T>(T TObject) where T : class
        {
            if (TObject is IUpdateOn)
            {
                (TObject as IUpdateOn).UpdateOn = DateTime.UtcNow;
            }

            var entry = db.Entry(TObject);
            db.Set<T>().Attach(TObject);
            entry.State = EntityState.Modified;
            return db.SaveChanges();
        }

        public virtual int Delete<T>(Expression<Func<T, bool>> predicate) where T : class
        {
            var objects = Filter<T>(predicate);
            foreach (var obj in objects)
                db.Set<T>().Remove(obj);
            return db.SaveChanges();
        }

        public bool Contains<T>(Expression<Func<T, bool>> predicate) where T : class
        {
            return db.Set<T>().Count<T>(predicate) > 0;
        }

        public virtual void ExecuteProcedure(String procedureCommand, params SqlParameter[] sqlParams)
        {
            db.Database.ExecuteSqlCommand(procedureCommand, sqlParams);

        }

        public virtual void SaveChanges()
        {
            db.SaveChanges();
        }

        public void Dispose()
        {
            if (db != null)
                db.Dispose();
        }
    }
}


4.

最後建立一個類別繼承 ApiController 實作 IIdentifier,且實作在 Api 中會使用的方法 ( CRUD )。

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;

using RMSApi.Models;
using RMSApi.Filters;
using RMSApi.Models.Repository;
using RMSApi.Models.Interface;
using System.Data;

namespace RMSApi.Controllers
{
    public class BaseApiController<T> : ApiController where T : class, IIdentifier
    {

        protected DefaultConnection db = new DefaultConnection();

        protected IRepository DataStore { get; set; }
        protected string[] Includes { get; set; }

        public BaseApiController()
        {
            this.DataStore = new Repository();
        }

        public virtual IEnumerable<T> Get()
        {
            return DataStore.All<T>(Includes);
        }

        public virtual T Get(Guid id)
        {
            return DataStore.Find<T>(t => t.ID == id, Includes);
        }

        public virtual void Post([FromBody]T value)
        {
            try
            {
                DataStore.Update<T>(value);
            }
            catch (OptimisticConcurrencyException ex)
            {
                throw ex;
            }
        }

        public virtual void Put([FromBody]T value)
        {
            DataStore.Create<T>(value);
        }

        public virtual void Delete(Guid id)
        {
            DataStore.Delete<T>(t => t.ID == id);
        }

        public virtual void Delete([FromBody]T value)
        {
            Delete(value.ID);
        }

        protected IEnumerable GetModelErrors()
        {
            return this.ModelState.SelectMany(x => x.Value.Errors.Select(error => error.ErrorMessage));
        }
    }
}

5.

最後在每一個 Controller 中都繼承 BaseApiController 並且帶入類別即可。

namespace RMSApi.Controllers
{
    public class CompanyController : BaseApiController
    {
        // 這裡是空的,真的是這樣
    }
}

這就是完整的把 Controller 的共同部分,也就是資料處理的部分抽離出來,讓你在 Controller 看到的程式碼更簡潔 ( 其實完全沒有了! )。如果你就在這 Controller 有獨特取得的資料方式,那就是在各隻 Controller 加上就好,比較直觀亦較好維護。




2013年11月11日 星期一

GC.SuppressFinalize 的用法

GC.SuppressFinalize ,通常使用在自己實作的 Dispose 使用之後,但我不太懂為什麼還要再使用 GC.SuppressFinalize ?

Dispose(true);
GC.SuppressFinalize(this);

後來我在網路上找到一段很有趣的解釋:

dispose告诉这个实体:哥不要你了,你可以去死了。
GC.SuppressFinalize(true); 这就是告诉系统,看到死尸了,让他去清理一下

其實光是 GC.SuppressFinalize(true) 就已經是個錯誤,正確用法是 GC.SuppressFinalize(this)

在 MSDN 上的解釋為「 要求系統不要為指定物件呼叫完成項 」,備註為「

這個方法會在物件標頭中設定位元,當系統呼叫完成項時會檢查這個位元。 obj 參數必須是這個方法的呼叫端。 如果 obj 沒有完成項,呼叫 SuppressFinalize 方法沒有作用。

實作 IDisposable 介面的物件會從 IDisposable.Dispose 方法呼叫這個方法,以免記憶體回收行程在不需要 Object.Finalize 的物件上呼叫它。



最後附上正確解釋,以下引用來自 [C#]Effective C# 條款十八:實現標準Dispose模式 的解釋:

具有解構子的物件其在被垃圾收集器回收處理時,會先被放入解構佇列之中,再交由另一個專門處理解構動作的執行緒去做解構的動作,當解構的動作完成,該物件又會被放回原來的佇列等待垃圾收集器的回收,因此其性能上的耗費會比沒有解構子的物件還來的多。由於IDisposable在實作上會習慣加入解構子做為保險措施,防止類別的使用者忘記叫用Dispose方法,造成資源的洩漏。故在釋放完資源後,我們應該隨即在後呼叫GC.SuppressFinalize,告知垃圾收集器該物件的解構動作跳過不處理。

2013年10月31日 星期四

如何 ASP.NET MVC 4 WebApi 跨域傳輸資料

ASP.NET MVC 4 WebApi 專案中,架構上把不同功能的 WebApi 全部都切開,也就是說,將每一塊功能的 Api 都設一個網域,將資料結構分散。因為WebApi 架設在雲端,所以如果某個專案負載過重,也只需要針對一組專案做環境上的調整。

現在問題來了,當我在做跨網域資料存取時,發生無法存取的問題,原因只是因為對 WebApi 存取預設只有自己的網域才可以,所以必須要設置允許網域。

方法 1.


直接修改 web.config ,不過這是針對所有 Action。
<location path="Sample.txt">
    <system.webServer>
      <httpProtocol>
        <customHeaders>
          <add name="Access-Control-Allow-Origin" value="*" />
        </customHeaders>
      </httpProtocol>
    </system.webServer>
</location>

方法 2.


加入一個類別,內容為以下所示:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

using System;
using System.Web.Http.Filters;

namespace Workflow.Filters
{
    public class AllowCrossSiteJsonAttribute : ActionFilterAttribute
    {
        public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
        {
            if (actionExecutedContext.Response != null)
                actionExecutedContext.Response.Headers.Add("Access-Control-Allow-Origin", "*");

            base.OnActionExecuted(actionExecutedContext);
        }
    }
}

最後你在 Controller 或者是 Action 上面加上屬性,即可允許跨網域傳輸資料:
    [AllowCrossSiteJson]
    public class InstancesController : ApiController
    {
        // ......

    }




2013年10月27日 星期日

ASP.NET MVC 4 中將資料寫入 Windows Azure Table Storage

繼前一篇 ASP.NET MVC 4 WebApi 中使用 ActionFilter 紀錄 Log 提到如何擷取出使用者使用 Action 的狀況,接下來就是將這些狀況紀錄到 Windows Azure Table Storage (儲存體) 內。

1.

首先,先將 Table Storage 的連接字串 (Connection String) 記下來,可以先到 Windows Azure 內找到這個資訊,請照下圖方式找到:

將這組組好的連接字串,放到 ASP.NET MVC 4 專案中的 Windows Azure Web Role 的組態檔內,多設置一組連結字串,照以下圖片設置:

2.

此時你就可以開始寫程式了,可以先將建立 Table Storage 連接:
CloudStorageAccount storageAccount = CloudStorageAccount.Parse(
    CloudConfigurationManager.GetSetting("StorageConnectionString"));

// 建立連接
CloudTableClient tableClient = storageAccount.CreateCloudTableClient();

在 Azure Table 內,每筆資料需要兩個主要的 Key,PartitionKey 和 RowKey,這兩個 Key 值可以組成唯一值。其實要看這兩筆 Key 很簡單,把 PartitionKey 看成是資料表名稱、 RowKey 就看成是主鍵,因為這兩組 Key 如果在 SQL Server 當然很好分辨,但是資料全部轉為 Table 格式,就只能這樣子去看待。

所以要先設定 PartitionKey 名稱,並且建立此表。
private const string LogTableName = "Logs";

...

// 假設表不存在則建立。
tableClient.CreateTableIfNotExist(LogTableName);

// 取得 Table 
TableServiceContext serviceContext = tableClient.GetDataServiceContext();

3.

接著設置一組類別,當作是 Table 的所有欄位,要繼承 TableServiceEntity,必須引用參考 Microsoft.WindowsAzure.StorageClient:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Runtime.Serialization;

using Microsoft.WindowsAzure.StorageClient;

namespace EnterprisePortal.Models
{
    [NotMapped]
    public class Log : TableServiceEntity
    {
        public Log(string partitionKey, string rowKey)
            : base(partitionKey, rowKey)
        {

        }

        public Log() : this("Logs", Guid.NewGuid().ToString())
        {
        }

        public string Location { get; set; }

        public string ServerIP { get; set; }

        public string PublicIP { get; set; }

        public string ErrorMessage { get; set; }

        public Guid Creater { get; set; }

    }
}

4.

可以開始使用這個類別對 Azure Table 做 CRUD 的動作了。
Log _log = new Log();

if (context.Exception == null)
{
    _log.Creater = Guid.NewGuid();
    _log.ErrorMessage = string.Empty;
    _log.Location = context.ActionContext.Request.RequestUri + " | " + context.ActionContext.Request.Method;
    _log.PublicIP = GetPublicIP();
    _log.ServerIP = GetServerIP();
}
else
{
    _log.Creater = Guid.NewGuid();
    _log.ErrorMessage = context.Exception.Message;
    _log.Location = context.Exception.TargetSite.DeclaringType.ToString() + " | " + context.Exception.TargetSite.Name;
    _log.PublicIP = GetPublicIP();
    _log.ServerIP = GetServerIP();
}

/* 刪除 */
List<Log> lstLogs =
(from e in serviceContext.CreateQuery<Log>(LogTableName)
 where e.PartitionKey == "Logs"
 select e).ToList();

foreach (var t_Log in lstLogs)
    serviceContext.DeleteObject(t_Log);

serviceContext.SaveChangesWithRetries();

/* 新增 */

serviceContext.AddObject(LogTableName, _log);

serviceContext.SaveChangesWithRetries();

/* 查詢 */
CloudTableQuery<Log> partitionQuery =
(from e in serviceContext.CreateQuery<Log>(LogTableName)
 where e.PartitionKey == "Logs"
 select e).AsTableServiceQuery<Log>();

 
foreach (Log entity in partitionQuery)
{
    Console.WriteLine("{0}, {1}\t{2}\t{3}", entity.PartitionKey, entity.RowKey,
        entity.PublicIP, entity.ErrorMessage);
}







2013年10月26日 星期六

ASP.NET MVC 4 WebApi 中使用 ActionFilter 紀錄 Log

在 ASP.NET MVC 4 中每個 Controller Action 執行期間,需要紀錄每個使用者的呼叫 Action 的狀態,一方面記錄使用過程,另一方面如果在此當中發生錯誤,紀錄此錯誤狀態訊息,好讓程式人員能夠依照錯誤訊息來改善系統功能。

首先必須要了解整個 Action 在觸發時,會執行哪一些 Filter:
圖片來源:http://www.cnblogs.com/me-sa/archive/2009/06/09/1499414.html

依上圖來看,事件的紀錄,不管是否產生錯誤,都可以在 OnActionExecuted 紀錄。

接下來說明一下在 ASP.NET MVC 4 WebApi 中 設置過程。

1.

新增一個類別檔在 Filters 底下,暫名為 WebApiActionFilterAttribute.cs,內容程式碼為:
namespace EnterprisePortal.Filters
{
    using System;
    using System.Net;
    using System.Net.Http;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;

    using System.Web.Http.Controllers;
    using System.Web.Http.Filters;
    using Microsoft.WindowsAzure;
    using Microsoft.WindowsAzure.StorageClient;
    using EnterprisePortal.Models;

    public class WebApiActionFilterAttribute : ActionFilterAttribute
    {
        private const string LogTableName = "Logs";

        public override void OnActionExecuting(HttpActionContext actionContext)
        {
            
        }

        public override void OnActionExecuted(HttpActionExecutedContext context)
        {
           ... 紀錄 Log,狀態都在 context 可以找到
            
        }
    }
}

2.

最後直接在你的 BaseApiController 中類別上加上 [WebApiActionFilter],或者直接在 Global.asax 註冊,程式碼如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

using System.Web.Http;
using EnterprisePortal.Models;
using EnterprisePortal.Filters;


namespace EnterprisePortal.Controllers
{
    [WebApiActionFilter]
    public class BaseApiController : ApiController
    {
        protected DefaultConnection db = new DefaultConnection();
    }
}

3.

測試吧! 隨便在一個 Action 丟出引數為無效的錯誤:
public IEnumerable Get()
{
    throw new ArgumentException();
    return new string[] { "value1", "value2" };
}

4.

最後在 WebApiActionFilterAttribute.cs 中,OnActionExecuted 設上中斷點,測試是否錯誤訊息有擷取到:


2013年10月20日 星期日

如何切換 Area 的 Web Api 路由 ( Route )

在 ASP.NET MVC 4 的專案中,建立 Areas ( 區域 ) 通常是在系統功能必須做切換的時機下使用,例如:網站前台與後台、或者電子商務系統可能會有店面、產品檢閱、使用者帳戶管理及購買系統 ... 等等。

區域可以有自己的 Model、 View 、Controller 模組,有獨立的網址路由和主版,提供很大的開發彈性。

為此,我選擇將網站中的其中一部分系統功能用 Areas 開發,但是我在 Backend 開了一個 Api Controller,名稱是 Member,處理會員資訊,如下圖。



我本以為這個 WebApi 的路徑會是 Backend/api/MemberApi/,可是實際路徑 api/MemberApi,後來我想如果別的 Areas 也有建一個 MemberApi,會怎麼樣?


會這樣。


最後就是它不知道該走哪一個路由。

此時我就找 BackendAreaRegistration.cs 這個 Areas 的路由檔案如下:

context.MapRoute(
    "Backend_default",
    "Backend/{controller}/{action}/{id}",
    new { action = "Index", id = UrlParameter.Optional }
);

這裡的路由是只對一般的 Controller,後來我 google 了一下解法,參考了這篇 ASP.NET MVC 4 WebAPI. Support Areas in HttpControllerSelector

1.

新增一個類別 AreaHttpControllerSelector.cs,目前是擺在 App_Start 底下。
namespace EnterprisePortal.Infrastructure.Dispatcher
{
    using System;
    using System.Collections.Concurrent;
    using System.Collections.Generic;
    using System.Globalization;
    using System.Linq;
    using System.Net.Http;
    using System.Web.Http;
    using System.Web.Http.Controllers;
    using System.Web.Http.Dispatcher;

    public class AreaHttpControllerSelector : DefaultHttpControllerSelector
    {
        private const string ControllerSuffix = "Controller";
        private const string AreaRouteVariableName = "area";

        private readonly HttpConfiguration _configuration;

        public AreaHttpControllerSelector(HttpConfiguration configuration)
            : base(configuration)
        {
            _configuration = configuration;
        }

        private Dictionary<string, Type> _apiControllerTypes;

        private Dictionary<string, Type> ApiControllerTypes
        {
            get { return _apiControllerTypes ?? (_apiControllerTypes = GetControllerTypes()); }
        }

        private static Dictionary<string, Type> GetControllerTypes()
        {
            var assemblies = AppDomain.CurrentDomain.GetAssemblies();

            var types = assemblies.SelectMany(a => a.GetTypes().Where(t => !t.IsAbstract && t.Name.EndsWith(ControllerSuffix) && typeof(IHttpController).IsAssignableFrom(t)))
                .ToDictionary(t => t.FullName, t => t);

            return types;
        }

        public override HttpControllerDescriptor SelectController(HttpRequestMessage request)
        {
            return GetApiController(request) ?? base.SelectController(request);
        }

        private static string GetAreaName(HttpRequestMessage request)
        {
            var data = request.GetRouteData();

            if (!data.Values.ContainsKey(AreaRouteVariableName))
            {
                return null;
            }

            return data.Values[AreaRouteVariableName].ToString().ToLower();
        }

        private Type GetControllerTypeByArea(string areaName, string controllerName)
        {
            var areaNameToFind = string.Format(".{0}.", areaName.ToLower());
            var controllerNameToFind = string.Format(".{0}{1}", controllerName, ControllerSuffix);

            return ApiControllerTypes.Where(t => t.Key.ToLower().Contains(areaNameToFind) && t.Key.EndsWith(controllerNameToFind, StringComparison.OrdinalIgnoreCase))
                    .Select(t => t.Value).FirstOrDefault();
        }

        private HttpControllerDescriptor GetApiController(HttpRequestMessage request)
        {
            var controllerName = base.GetControllerName(request);

            var areaName = GetAreaName(request);
            if (string.IsNullOrEmpty(areaName))
            {
                return null;
            }

            var type = GetControllerTypeByArea(areaName, controllerName);
            if (type == null)
            {
                return null;
            }

            return new HttpControllerDescriptor(_configuration, controllerName, type);
        }
    }
}

2.

然後在 Global.asax 引用兩個命名空間。
using EnterprisePortal.Infrastructure.Dispatcher;
using System.Web.Http.Dispatcher;

最後在 Application_Start() 加上
GlobalConfiguration.Configuration.Services.Replace(typeof(IHttpControllerSelector), new AreaHttpControllerSelector(GlobalConfiguration.Configuration));
            
RouteTable.Routes.MapHttpRoute(
    name: "AreaApi",
    routeTemplate: "api/{area}/{controller}/{id}",
    defaults: new { id = RouteParameter.Optional }
);

3.

運行後即可。



2013年10月19日 星期六

Team Foundation Server (TFS) 初次取得線上版本

最近小弟因工作需求必須要使用 Team Foundation Server 上的功能,與 SVN 使用上其實很不一樣,往後會發佈相關教學說明,現在就來介紹初次如何取得線上版本。

1.

首先,必須要先決定線上版本的程式碼要放在本機的哪個目錄中,此範例就先暫訂存在 「D:\網站\開發站\」 底下。

2.

接著開啟 Visual Studio 2012,檢視 > Team Explorer,右側欄出現 Team Explorer 視窗

※ 若對應不到本機端資料夾,照以下方式處理:動作 > 管理工作區

選定 TFS 的資料夾

設定本機資料夾,按下確定

按下是


3.

最後就可以看到 TFS 上的最新版本已經複製到所設定的資料夾內。




將網站部署到 Windows Azure

繼前一篇 如何將 SQL Server 資料庫部署到 SQL Azure,今天就來介紹將網站部署到 Windows Azure。

1.

至 Windows Azure 選擇網站,並決定要部屬的網站名稱。

2.

下載發行設定檔 ( 副檔名為 PublishSettings )

必須妥善保管此檔案

3.

選擇網站要使用的 SQL Azure,並且取得它的連接 字串,其中密碼是不顯示在上面的, 必須修改密碼部分才是完整的 連接字串

4.

針對要上傳的專案點擊「發行」

5.

設置發行設定檔,若在伺服器總管中有設定 Windows Azure 網站, 亦可直接設置。


6.

選定好發行設定檔或者 Windows Azure 網站,按下一個就會帶出網站的設定資訊。可按下驗證連線確定無誤。

7.

接下來要設定資料連接字串,再來就需要複製第 3 步驟的資料庫字串置換密碼後貼到這裡。

8.

最後確認上傳檔案。按下發行。

9.

發行完就可以確認網站是否運行。打完收工



2013年10月18日 星期五

如何將 SQL Server 資料庫部署到 SQL Azure

最近小弟因工作需求必須要使用 Windows Azure 上的功能,今天就來介紹將 SQL Server 資料庫部署到 SQL Azure 的步驟。

1.

在 SQL Server 選定要同步的資料庫,點選右鍵 > 工作 > 將資料庫部署到 SQL Azure

2.

下一步

3.

此步驟要連接資料庫,可設定網域內的 SQL Server 或者是 SQL Azure 的,在此連接字串可從 Windows Azure 取得在 Key-in 到連接資訊中。( 機密資訊已處理 )



4.

確認部署詳細資料

5.

部署中...

6.

請注意,每個資料表必須要有叢集索引 ( Clustering Index ),要不然只要一個資料表失敗就會整個 Rollback。

7.

最後,雲端上就會多出剛剛部署完的資料庫。

8.

打完收工



2013年9月25日 星期三

ASP.NET C# EXCEL 檔案上傳不儲存檔案讀取資料

在以往的檔案上傳,都要先將上傳的檔案存到伺服器端的某個目錄,還必須要將檔案命名成獨立的名稱,不然同時有可能會發生有人檔案上傳失敗。

但是如果不存實體檔案,存到一個暫存空間 ( MemoryStream ),再將資料轉換成 DataTable 或者是 IDataReader ,最後資料讀取出來或傳入 GridView 使用。

1.

首先先下載 ClosedXML:http://closedxml.codeplex.com/releases/view/110822 並引入參考。

2.

撰寫一個類別使用 ClosedXML 寫常用的方法到時方便使用。 ( 以下是參考程式碼,可依照狀況不同調整 )
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using System.Data.OleDb;
using System.IO;
using ClosedXML.Excel;

namespace FileHanding
{
    //ClosedXML Documentation: http://closedxml.codeplex.com/documentation
    public class FileHanding
    {
        public MemoryStream TransferDataTableToExcel(DataTable dt)
        {
            var wb = new XLWorkbook();
            wb.Worksheets.Add(dt);
            MemoryStream ms = new MemoryStream();
            wb.SaveAs(ms);
            return ms;
        }

        public DataTable TransferExcelToDataTable(byte[] file)  
        {
            Stream fileStream = new MemoryStream(file);
            var workbook = new XLWorkbook(fileStream);
            var xlWorksheet = workbook.Worksheet(1);
            return TransferExcelToDataTable(xlWorksheet);
        }

        public DataTable TransferExcelToDataTable(byte[] file, string sheetName)
        {
            Stream fileStream = new MemoryStream(file);
            var workbook = new XLWorkbook(fileStream);
            var xlWorksheet = workbook.Worksheet(sheetName);
            return TransferExcelToDataTable(xlWorksheet);
        }

        public DataTable TransferExcelToDataTable(string filePath)
        {
            var workbook = new XLWorkbook(filePath);
            var xlWorksheet = workbook.Worksheet(1);
            return TransferExcelToDataTable(xlWorksheet);
        }

        public DataTable TransferExcelToDataTable(string filePath, string sheetName)
        {
            var workbook = new XLWorkbook(filePath);
            var xlWorksheet = workbook.Worksheet(sheetName);
            return TransferExcelToDataTable(xlWorksheet);
        }

        private DataTable TransferExcelToDataTable(IXLWorksheet xlWorksheet)
        {
            var datatable = new DataTable();
            var range = xlWorksheet.Range(xlWorksheet.FirstCellUsed(), xlWorksheet.LastCellUsed());

            int col = range.ColumnCount();
            int row = range.RowCount();

            // add columns hedars
            datatable.Clear();

            for (int i = 1; i <= col; i++)
            {
                IXLCell column = xlWorksheet.Cell(1, i);
                datatable.Columns.Add(column.Value.ToString());
            }

            // add rows data   
            int firstHeadRow = 0;
            foreach (var item in range.Rows())
            {
                if (firstHeadRow != 0)
                {
                    var array = new object[col];
                    for (int y = 1; y <= col; y++)
                    {
                        array[y - 1] = item.Cell(y).Value;
                    }
                    datatable.Rows.Add(array);
                }
                firstHeadRow++;
            }
            return datatable;
        }

        
        public IDataReader TransferExcelToIDataReader(byte[] file)
        {
            Stream fileStream = new MemoryStream(file);
            var workbook = new XLWorkbook(fileStream);
            var xlWorksheet = workbook.Worksheet(1);
            return TransferExcelToIDataReader(xlWorksheet);
        }

        public IDataReader TransferExcelToIDataReader(byte[] file, string sheetName)
        {
            Stream fileStream = new MemoryStream(file);
            var workbook = new XLWorkbook(fileStream);
            var xlWorksheet = workbook.Worksheet(sheetName);
            return TransferExcelToIDataReader(xlWorksheet);
        }

        public IDataReader TransferExcelToIDataReader(string filePath)
        {
            var workbook = new XLWorkbook(filePath);
            var xlWorksheet = workbook.Worksheet(1);
            return TransferExcelToIDataReader(xlWorksheet);
        }

        public IDataReader TransferExcelToIDataReader(string filePath, string sheetName)
        {
            var workbook = new XLWorkbook(filePath);
            var xlWorksheet = workbook.Worksheet(sheetName);
            return TransferExcelToIDataReader(xlWorksheet);
        }

        private IDataReader TransferExcelToIDataReader(IXLWorksheet xlWorksheet)
        {
            var datatable = new DataTable();
            var range = xlWorksheet.Range(xlWorksheet.FirstCellUsed(), xlWorksheet.LastCellUsed());

            int col = range.ColumnCount();
            int row = range.RowCount();

            // add columns hedars
            datatable.Clear();

            for (int i = 1; i <= col; i++)
            {
                IXLCell column = xlWorksheet.Cell(1, i);
                datatable.Columns.Add(column.Value.ToString());
            }

            // add rows data   
            int firstHeadRow = 0;
            foreach (var item in range.Rows())
            {
                if (firstHeadRow != 0)
                {
                    var array = new object[col];
                    for (int y = 1; y <= col; y++)
                    {
                        array[y - 1] = item.Cell(y).Value;
                    }
                    datatable.Rows.Add(array);
                }
                firstHeadRow++;
            }
            return datatable.CreateDataReader();
        }



        public MemoryStream TransferDataTableToCsv(DataTable dt)
        {
            MemoryStream ms = new MemoryStream();
            StreamWriter result = new StreamWriter(ms, Encoding.UTF8);

            //Header
            for (int i = 0; i < dt.Columns.Count; i++)
            {
                result.Write(dt.Columns[i].ColumnName);
                result.Write(i == dt.Columns.Count - 1 ? "\n" : ",");
            }

            //Content
            foreach (DataRow row in dt.Rows)
            {
                for (int i = 0; i < dt.Columns.Count; i++)
                {
                    result.Write(row[i].ToString());
                    result.Write(i == dt.Columns.Count - 1 ? "\n" : ",");
                }
            }
            
            return ms;
        }

        public DataTable TransferCsvToDataTable(string strFilePath)
        {
            string strFileName = Path.GetFileName(strFilePath);
            string strFileDirectory = Path.GetDirectoryName(strFilePath);
            string strConn = string.Format(@"Provider=Microsoft.Jet.OLEDB.4.0;Data Source={0}\;Extended Properties='Text;HDR=Yes;'", strFileDirectory);
            string strSQL = string.Format("SELECT * FROM [{0}]", strFileName);
            OleDbDataAdapter adapter = new OleDbDataAdapter(strSQL, strConn);
            DataTable dt = new DataTable();
            adapter.Fill(dt);
            return dt;
        }

    }
}

3.

介面上,只需要用一般的 input 加上 runat="server" 即可,利用 Postback 將檔案讀取。以下是介面圖:

再看看程式碼快照:

4.

在上傳檔案時,將檔案轉換成 byte[],並傳入已經寫好的類別 ( FileHanding ) 方法進行轉換,以下為參考程式碼:
using FileHanding;

...
...
...
...
...
...

protected void btnImport_Click(object sender, EventArgs e)
{
    if (IsPostBack)
    {
        if (file.PostedFile != null)
        {
            FileHanding fh = new FileHanding();;
            string strErrorMessage = string.Empty;
            var postedFile = file.PostedFile;
            int iDataLength = postedFile.ContentLength;
            byte[] bData = new byte[iDataLength];
            bool bIsVerify = true;
            postedFile.InputStream.Read(bData, 0, iDataLength);

            if (txtSheetName.Text.Trim() == string.Empty)
                dt = fh.TransferExcelToDataTable(bData);
            else
                dt = fh.TransferExcelToDataTable(bData, txtSheetName.Text);

            /* 以下再做資料處理 */ 


        }
    }
}





2013年9月23日 星期一

如何在 .aspx 將資料 post 到 .ashx

最近小弟要做一個活動頁面,此頁面是投票活動,要綁會員投票資訊,但是未登入者也可以觀看此頁面。

使用者給我的介面有兩個步驟完成投票,第一個步驟有兩個區塊,一個是即時登入頁面,一個是即時註冊頁面;第二個步驟是投票選項,這兩個步驟都選定了才能將資料送出。

附帶條件是不能利用連結導向到登入頁面和註冊頁面,因為怕瀏覽者點走了就不知道怎麼回來,所以只能在當頁將所有動作完成。

遇到這個狀況,我想說如果使用 ajax 送出資料到另一個頁面去註冊或者做登入,那成功或錯誤訊息送回來,我要怎麼將傳回來的 MemberID 存起來,隱藏欄位?cookie?最後決定使用 post 到 ashx 再將 MemberID 傳回來,再將傳回來的資料存在session內,繼續做投票動作。

於是 我參考了 [ASP.NET]在server端post資料到.ashx 內的程式碼來解決問題。

LoginHander.ashx.cs
public class LoginHandler : IHttpHandler, IRequiresSessionState
{
    public void ProcessRequest(HttpContext context)
    {
        context.Response.ContentType = "text/plain";

        string strID = string.Empty;
        string strMsg = string.Empty;
        string strEMail = context.Request.Form["Email"];
        string strPassword = context.Request.Form["Password"];

        if (string.IsNullOrEmpty(strEMail))
        {
            strMsg += (strMsg == string.Empty) ? "Please input email address.<br/>" : "";
        }

        if (string.IsNullOrEmpty(strPassword))
        {
            strMsg += (strMsg == string.Empty) ? "Please input password.<br/>" : "";
        }
        
        //建立會員資料查詢物件
       
        string msg;

        bool isSuccess = /* 檢查帳密是否正確 */
        if (!isSuccess)
        {
            strMsg += (strMsg == string.Empty) ? "Sorry, unrecognized email address or password.<br/>" : "";
        }

  /* 額外判斷 */
  
  strID = /* 取得會員ID */
  
        if (string.IsNullOrWhiteSpace(strMsg))
        {
            context.Response.Write("true," + strID);

        }
        else
            context.Response.Write(strMsg);
    }

    public bool IsReusable
    {
        get
        {
            return false;
        }
    }
}

.aspx.cs 中的按鈕
protected void btnSubmitLogin_Click(object sender, EventArgs e)
{
    lblLoginErrorMessage.Text = string.Empty;
    var url = "http://www.sample.com/../../LoginHandler.ashx"; // 網址路徑 

    WebRequest request = WebRequest.Create(url);
    request.Method = "POST";

    // server 端可以透過 Request.Form["Email"] 和 Request.Form["Password"] 讀取
    string postData = string.Format("Email={0}&Password={1}", txtEMail.Text, txtPassword.Text);

    byte[] byteArray = Encoding.UTF8.GetBytes(postData);

    request.ContentType = "application/x-www-form-urlencoded";

    // Set the ContentLength property of the WebRequest.
    request.ContentLength = byteArray.Length;

    // Get the request stream.
    using (Stream dataStream = request.GetRequestStream())
    {
        // Write the data to the request stream.
        dataStream.Write(byteArray, 0, byteArray.Length);
    }

    // Get the response.      
    using (WebResponse response = request.GetResponse())
    {
        using (Stream dataStream = response.GetResponseStream())
        {
            using (StreamReader reader = new StreamReader(dataStream))
            {
                string responseFromServer = reader.ReadToEnd();
    
    /* 回傳資料若開頭為 true */
                if (responseFromServer.Trim().StartsWith("true"))
                {
     /* 紀錄 ID */
                    Session["MemberID"] = responseFromServer.Trim().Replace("true,", "");

     /* 後續資料處理 */
                }
                else
                {
     /* 錯誤訊息顯示 */
                    lblLoginErrorMessage.Text = responseFromServer.Trim();
                }
            }
        }
    }
}





2013年9月11日 星期三

ASP.NET 中關於 Postback 與離開頁面的分辨



大家在使用Asp.net開發應用系統時,有時會存在這樣的場景,當頁面離開時需要清空Session等等一系列的後序操作。

如果大家使用後台代碼清空Session,那麼當頁面離開時是不會Postback頁面的,問題就出在這裡,那麼我們只能從前台入手了,可以使用前台事件window.onunload,沒有問題,我們繼續,當我們正常的載入Asp.Net頁面,點擊一個Button,頁面PostBack之前每次都會觸發onunload事件,那麼我們如何才能在前台利用onunload事件分析出在什麼情況下是postback,什麼情況下是離開呢?

    var isSubmit = false;
    $(document).ready(function () {
        document.forms[0].onsubmit = SubmitFun;
        window.onunload = CheckAction;
    });
 
    function SubmitFun()
    {
        isSubmit = true;
    }
 
    function CheckAction() {
        if (!isSubmit) {

            var guid = $('#<%=hfGuid.ClientID %>').val();
            
            // ajax 執行刪除某一系列 Session 的動作
            // $.get('<%=ResolveClientUrl("...")%>', function () { });
            
            alert("is leave");
            //to do 
            //for example use the ajax invoke service method to clean session 
        }
        else {
            alert("is submit");
            //to do
        }
    }

我們結合使用window.onunload與form.onsubmit兩個事件來完成分辨,當提交一個form時,觸發onsubmit事件,我們在這個事件處理方法中,將isSubmit置為true,在onunload事件中,根據isSubmit去分辨是提交頁面還是頁面離開,由於onunload事件總是在onsubmit事件之後執行,所以可以保證得到正確的結果。

 在註釋處我們可以添加自己想要的代碼,例如使用ajax調用後台方法等等...


引用:Web系统中关于Postback与页面离开的分辨

2013年8月28日 星期三

document 文件對象 - JavaScript腳本語言描述

頁面上元素name屬性和JavaScript引用的名稱必須一致包括大小寫
否則會提示你一個錯誤信息"引用的元素為空或者不是對象"
-------------------------------------------------- -------------------

對象屬性
document.title // 取得或設定文件的標題
document.bgColor // 取得或設定文件背景的顏色,文件背景顏色由 CSS 中的 background-color 性質設定
document.fgColor // 改變前景文字顏色的屬性
document.linkColor // 設定 <a> 元素 (element) 文字顏色的物件
document.alinkColor // 設定 <a> 元素 (element) 在滑鼠點擊瞬間顏色的物件
document.vlinkColor // 設定 <a> 元素 (element) 拜訪過的顏色物件
document.URL // 取得目前網址
document.fileCreatedDate // 文件建立日期,唯讀屬性
document.fileModifiedDate // 文件修改日期,唯讀屬性
document.fileSize // 文件大小,唯讀屬性
document.cookie // 取得或設定 cookie
document.charset // 設定字符集簡體中文:gb2312
-------------------------------------------------- -------------------
常用對象方法
document.write() // 動態向頁面寫入內容
document.createElement(Tag) // 創建一個html標籤對象
document.getElementById(ID) // 獲得指定ID值的對象
document.getElementsByName(Name) // 獲得指定Name值的對象
document.body.appendChild(oTag)
-------------------------------------------------- -------------------

body-主體子對象
document.body // 指定文檔主體的開始和結束等價於<body></body>
document.body.bgColor // 取得或設定文件主體背景的顏色
document.body.link // 設定 <a> 元素 (element) 文字顏色的物件
document.body.alink // 設定 <a> 元素 (element) 在滑鼠點擊瞬間顏色的物件
document.body.vlink // 設定 <a> 元素 (element) 拜訪過的顏色物件
document.body.text // 文本色
document.body.innerText // 取得或設定 <body>...</body> 之間的文本
document.body.innerHTML // 取得或設定 <body>...</body> 之間的HTML代碼
document.body.topMargin // 頁面上邊距
document.body.leftMargin // 頁面左邊距
document.body.rightMargin // 頁面右邊距
document.body.bottomMargin // 頁面下邊距
document.body.background // 背景圖片

document.body.appendChild(oTag) // 動態生成一個HTML對象
常用對象事件
document.body.onclick="func()" // 鼠標指針單擊對像是觸發
document.body.onmouseover="func()" // 鼠標指針移到對象時觸發
document.body.onmouseout="func()" // 鼠標指針移出對象時觸發
-------------------------------------------------- -------------------
location - 位置子對象
document.location.hash // # 號後的部分
document.location.host // 域名 + 埠號
document.location.hostname // 域名
document.location.href // 完整URL
document.location.pathname // 目錄部分
document.location.port // 埠號
document.location.protocol // 網絡協議(http:)
document.location.search // ? 號後的部分

documeny.location.reload() // 重新整理網頁
document.location.reload(URL) // 打開新的網頁
document.location.assign(URL) // 打開新的網頁
document.location.replace(URL) // 打開新的網頁
-------------------------------------------------- -------------------
selection-選區子對象
document.selection
-------------------------------------------------- -------------------

images集合(頁面中的圖像)

a) 通過集合引用
document.images //對應頁面上的img標籤
document.images.length //對應頁面上img標籤的個數
document.images[0] //第1個img標籤
document.images[i] //第i-1個img標籤
b) 通過nane屬性直接引用
img name="oImage"
document.images.oImage //document.images.name屬性
c) 引用圖片的src屬性
document.images.oImage.src //document.images.name屬性.src
d) 創建一個圖像
var oImage
oImage = new Image()
document.images.oImage.src="1.jpg"
同時在頁面上建立一個 img 標籤與之對應就可以顯示

-------------------------------------------------- --------------------

forms集合(頁面中的表單)

a)通過集合引用
document.forms // 對應頁面上的form標籤
document.forms.length // 對應頁面上 form 標籤的個數
document.forms[0] // 第1個 form 標籤
document.forms[i] // 第i - 1個 form 標籤
document.forms[i].length // 第i - 1個 form 中的控制項
document.forms[i].elements[j] // 第i - 1個 form 中第j - 1個控制項
b)通過標籤name屬性直接引用
<form name="Myform"><input name="myctrl"/></form>
document.Myform.myctrl // document.表單名.控制項名
c)訪問表單的屬性
document.forms[i].name //對應<form name>屬性
document.forms[i].action //對應<form action>屬性
document.forms[i].encoding //對應<form enctype>屬性
document.forms[i].target //對應<form target>屬性

document.forms[i].appendChild(oTag) //動態插入一個控件
document.all.oDiv //引用圖層oDiv
document.all.oDiv.style.display="" //圖層設置為可視
document.all.oDiv.style.display="none" //圖層設置為隱藏
document.getElementId("oDiv") //通過getElementId引用對象
document.getElementId("oDiv").style=""
document.getElementId("oDiv").display="none"
/*document.all表示document中所有對象的集合
只有ie支持此屬性,因此也用來判斷瀏覽器的種類*/

圖層對象的4個屬性
document.getElementById("ID").innerText //動態輸出文本
document.getElementById("ID").innerHTML //動態輸出HTML
document.getElementById("ID").outerText //同innerText
document.getElementById("ID").outerHTML //同innerHTML

引用網址: http://www.ccvita.com/80.html
參考網址:http://pydoing.blogspot.tw/2011/08/javascript-url.html

回首頁

2013年8月15日 星期四

SQL使用WebService取得資料

以前從沒想過SQL SERVER可以使用WebService取得資料,最近因工作需要看到了SSIS可以做到(SSIS-如何接收web services的資料當做資料來源做轉檔),但是發現我需要使用的WebService傳入參數是他自己指定的Type,我從資料庫取出資料再指定變數給WebService會發生不認識的情況而無法正常取值,後來無意間發現了原來SQL SERVER 2005之後就可以用SQL語法直接使用WebService。
declare @sUrl varchar(200)
set @sUrl='http://www.webservicex.net/CurrencyConvertor.asmx/ConversionRate?FromCurrency=USD&ToCurrency=TWD'  --設定Url

Declare @Object as Int; --宣告物件
Declare @ResponseText as Varchar(max); --宣告回傳資料

Exec sp_OACreate 'MSXML2.XMLHTTP', @Object OUT; --創造物件
Exec sp_OAMethod @Object, 'open', NULL, 'get',@sUrl,false  --開啟
Exec sp_OAMethod @Object, 'send'  --執行
Exec sp_OAMethod @Object, 'responseText', @ResponseText OUTPUT   --取回資料
Exec sp_OADestroy @Object   --釋放
Select @ResponseText

這樣就可以取得WebService所回傳的資料,但回傳的是XML格式,所以還需要使用XQuery把值取出來。
因為這個WebService只有回傳一個Double的數值,不需要很複雜的指令,最後再Insert到Table 搞定
declare @sUrl varchar(200)
declare @xml xml --宣告XML變數
set @xml=@ResponseText --放資料
select convert(varchar,@xml.query('data(/)')) --XML要先轉字串,沒辦法直接指定其他型別

以上
目前沒時間詳細研究XQuery,日後有機會再玩玩吧
回sql目錄
回首頁

2013年4月13日 星期六

Wordpress Plugins:SEO Rank Report 關鍵字排名追蹤報表

「搜尋查詢」網頁會針對從您網站傳回網頁的「Google 網頁搜尋」查詢提供相關資訊。

SEO Rank Report 這個 Wordpress Plugin 就是幫你去 Google 網頁於你所輸入網頁是否出現在結果的位置。

安裝、使用方法如下:

1



2

後台左側選單中可以看到底下這些選項,可以依照 Rank Report -> Add Keywords 點擊。
而 Rank Report -> Settings 中能設定從哪個國家語言的 google 搜尋引擎下去做搜尋,這會更接近實際情況。


3

針對你網站以及關鍵字做條件。


4

預覽你的下達的網站和關鍵字排名,並選定哪一個是最接近關鍵字的網址,以便往後追蹤。


5

最後觀看圖表,並在每三日更新ㄧ次。


回首頁

2013年4月12日 星期五

Wordpress Plugins:Xml Sitemap 自動產生 sitemap.xml

Google 可能無法找到您網站上的所有網頁,而 Sitemap 正好可以彌補不足的資訊。簡單來說,XML Sitemap (通常稱為 Sitemap,字首 S 大寫) 就是網站中的網頁清單。建立並提交 Sitemap 可協助 Google 掌握您網站上的所有網頁,包括一般的 Google 檢索程序可能檢索不到的網址。

您可以根據 Sitemap 通訊協定來建立 Sitemap,或者您也可以提交文字檔或 RSS/Atom 資訊提供做為 Sitemap。

此外,您也可以使用 Sitemap 向 Google 提供與特定網站內容類型相關的中繼資料,包括影片、圖片、行動服務和新聞。例如:影片 Sitemap 項目可以標示影片的播放時間、類別以及是否適合闔家觀賞;圖片 Sitemap 項目可以提供圖片主題、類型與授權的相關資訊。您也可以透過 Sitemap 來提供網站的其他資訊,例如:網站上次更新的日期,以及預計的網頁更新頻率。我們建議您使用單獨的 Sitemap 來提交新聞資訊。

Wordpress 可以透過 Google XML Sitemaps 這個外掛來自動以及定期產生 sitemap.xml,此 Plugin 是本人覺得最好用的外掛。

安裝、使用方法如下:

1

至安裝外掛頁面搜尋並安裝外掛。


2

如果是第一次產生可以按下「點這裡」來產生 sitemap.xml。


以下是參數設定。

3

基本選項可以設定 xml 是否壓縮以及可以透過 GET 方式來產生 sitemap.xml,以便我不用登入後台即可以產生。


4

網站地圖內容可以讓系統知道在產生 sitemap.xml 的時候,必須包含哪些內容。


5

更新頻率設定說明在 sitemap.xml 中,這些內容多久會更新ㄧ次,也讓機器人知道它要多久來抓ㄧ次。


6

網站地圖的位置,這裡它會自動預設,所以不需要特別去設定。


7

文章優先權是讓 Plugin 依照 迴響數目 或 迴響平均值 或 不設定,來算出頁面個別的優先權。


8

優先權與第 7 點有關,這裡是將各類型的內容訂定優先權分數。


Google 無法保證一定會檢索您的所有網址或是為您的所有網址建立索引。不過,我們會透過您 Sitemap 中的資料來瞭解網站的結構,進而在日後改善檢索器的排程方式,更有效地檢索您的網站。提交 Sitemap 絕對不會造成負面影響;反之,在多數情況下,網站管理員都能因此而受惠。

參考:關於 Sitemap

回首頁