2016年8月5日 星期五

asp.net webapi jsonp跨網域取得資料


在asp.net Web API中寫jsonp,是不用更動到原本的API程式的,非常的簡單

View
$.ajax({
    url: 'http://localhost:53322/api/values',
    type: 'get',
    dataType: 'jsonp',
    success: function (e) {
  console.log(e);
    }
});

API Controller

public IEnumerable<string> Get()
{
    return new string[] { "value1", "value2" };
}

上面兩個jquery和web api的範例應該很多人都很熟了

下面是最重要的,必須在Global.asax.cs中,新增一個繼承自JsonMediaTypeFormatter的類別,我命名為JsonpMediaTypeFormatter
protected void Application_Start()
{
 ……
 var config = GlobalConfiguration.Configuration;
 config.Formatters.Insert(0, new JsonpMediaTypeFormatter());
}

public class JsonpMediaTypeFormatter : JsonMediaTypeFormatter
{
    private string callbackQueryParameter;

    public JsonpMediaTypeFormatter()
    {
        SupportedMediaTypes.Add(DefaultMediaType);
        SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/javascript"));

        MediaTypeMappings.Add(new UriPathExtensionMapping("jsonp", DefaultMediaType));
    }

    public string CallbackQueryParameter
    {
        get { return callbackQueryParameter ?? "callback"; }
        set { callbackQueryParameter = value; }
    }

    public override Task WriteToStreamAsync(Type type, object value, Stream stream, HttpContent content, TransportContext transportContext)
    {
        string callback;

        if (IsJsonpRequest(out callback))
        {
            return Task.Factory.StartNew(() =>
            {
                var writer = new StreamWriter(stream);
                writer.Write(callback + "(");
                writer.Flush();

                base.WriteToStreamAsync(type, value, stream, content, transportContext).Wait();

                writer.Write(")");
                writer.Flush();
            });
        }
        else
        {
            return base.WriteToStreamAsync(type, value, stream, content, transportContext);
        }
    }

    private bool IsJsonpRequest(out string callback)
    {
        callback = null;

        if (HttpContext.Current.Request.HttpMethod != "GET")
            return false;
         callback = HttpContext.Current.Request.QueryString[CallbackQueryParameter];

        return !string.IsNullOrEmpty(callback);
    }
}

JsonpMediaTypeFormatter的作用是將jquery傳到web api的參數(callback),把ValuesController回傳的json資料包裝成一個function後回傳

所以我們就不用在ValuesController裡面處理Callback參數了




DB first Model更新時, 手動修改的class資料會被清除


如果開發者是使用DB first Model應該都會有這種經驗,我們為了要驗證欄位填寫是否正確,會在model的class中加上一些驗證屬性
public partial class book
{
    [Required(ErrorMessage = "請輸入書名")]
    public string bookName{ get; set; }
}

可是當我們使用開發工具更新Model.edmx時,我們所新增的驗證欄位會被全部清除。以前我索性就不要使用驗證的功能了,自行在前端撰寫驗證程式。或是簡單點,在text屬性中加入required就好了

直到我在書上有看到解決方式

1. 在Model資料夾中新增一個Partials資料夾,並新增一個同名的class(book.cs)














2. 將原本book.cs中的程式複製到Partials.book中
namespace Sample.Models
{
    [MetadataType(typeof(bookMetadata))]
    public partial class book
    {
        public class bookMetadata
        {
             [Required(ErrorMessage = "請輸入書名")]
             public string bookName { get; set; }
        }
    }
}

3. 在建立Partials資料夾時,資料夾中的class的命名空間會自動加上Partials(Sample.Models.Partials)。記得要把.Partials移除,不然不會成功

4. 在編譯時,會將兩個book.cs合成同一個,使用上必須是同一個專案,同樣的命名空間,同樣的class名稱


參考資料:ASP.NET MVC 開發美學



2016年8月3日 星期三

當從Excel or Word中開啟一個含有session(cookie)的網頁連結,session(cookie)會消失



MS Office是使用一個叫做Hlink.dll的元件來判斷文件中的連結是要開啟一個MS Office檔案, 或是其他東西(browser)。

假設Excel中的目標網頁是http://localhost/manager/EditNews.aspx,這個網頁是需要登入才可以進入的。在沒有登入的情況下會redirect到登入頁http://localhost/login.aspx?ReturnUrl=news.aspx。而我實際上已經有登入過了(session已存在)

所以我從Excel開啟了http://localhost/manager/news.aspx,應該會正常連結到目標網頁才對,但是我總是會連結到http://localhost/login.aspx?ReturnUrl=news.aspx。這是不是就像是session已經消失了......其實session沒有消失

想像Excel(Hlink.dll)是這樣運作的......
當一個連結需要用browser開啟時,在Excel內部會先執行http://localhost/manager/news.aspx,因為在內部執行,所以沒有session,因此就會導向登入頁(http://localhost/login.aspx?ReturnUrl=news.aspx)了。

在參考資料中,有人是使用header refresh的方式來解決這個問題,但是我不適用,有需要的人也可以參考看看。

而我是使用javascript setTimeout的方式。因為我在登入頁有加上ReturnUrl的參數,所以我可以用來當作判斷返回的依據

.cs
if (Request.QueryString["ReturnUrl"].Contains("news") && Session["login"] != null)
{
   news.Value = "true";
   ReturnUrl.Value = Request.QueryString["ReturnUrl"];
}

.aspx
<asp:HiddenField ID="news" runat="server" />
<asp:HiddenField ID="ReturnUrl" runat="server" />
$(function () {
    if ($('#news').val() === "true") {
        setTimeout(function () {
            location.href = "manager/" + $('#ReturnUrl').val();
        }, 100);
    }
});

如果參數設定的適當,在登入頁暫停一下再連結到目標網頁,應該就可以解決問題了


參考資料:http://stackoverflow.com/questions/2653626/why-are-cookies-unrecognized-when-a-link-is-clicked-from-an-external-source-i-e