2014年5月19日 星期一

執行 API Controller 遇到 「Not running in a hosted service or the Development Fabric.」 問題之解決方案

建置 API Controller 時,執行後遇到以下錯誤訊息:

Not running in a hosted service or the Development Fabric.

描述: 在執行目前 Web 要求的過程中發生未處理的例外狀況。請檢閱堆疊追蹤以取得錯誤的詳細資訊,以及在程式碼中產生的位置。

例外狀況詳細資訊: System.InvalidOperationException: Not running in a hosted service or the Development Fabric.

原始程式錯誤:

在執行目前 Web 要求期間,產生未處理的例外狀況。如需有關例外狀況來源與位置的資訊,可以使用下列的例外狀況堆疊追蹤取得。

堆疊追蹤:

[InvalidOperationException: Not running in a hosted service or the Development Fabric.]
   Microsoft.WindowsAzure.Diagnostics.DiagnosticMonitor.GetDefaultStartupInfoForCurrentRoleInstance() +173
   Microsoft.WindowsAzure.Diagnostics.DiagnosticMonitorTraceListener..ctor() +59

[ConfigurationErrorsException: 無法建立 Microsoft.WindowsAzure.Diagnostics.DiagnosticMonitorTraceListener, Microsoft.WindowsAzure.Diagnostics, Version=2.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35。]
   System.Diagnostics.TraceUtils.GetRuntimeObject(String className, Type baseType, String initializeData) +6707013
   System.Diagnostics.TypedElement.BaseGetRuntimeObject() +45
   System.Diagnostics.ListenerElement.GetRuntimeObject() +83
   System.Diagnostics.ListenerElementsCollection.GetRuntimeObject() +142
   System.Diagnostics.TraceInternal.get_Listeners() +181
   System.Diagnostics.TraceInternal.TraceEvent(TraceEventType eventType, Int32 id, String format, Object[] args) +155
   System.Diagnostics.Trace.TraceInformation(String message) +14
   System.Web.Http.Tracing.SystemDiagnosticsTraceWriter.TraceMessage(TraceLevel level, String message) +157
   System.Web.Http.Tracing.SystemDiagnosticsTraceWriter.Trace(HttpRequestMessage request, String category, TraceLevel level, Action`1 traceAction) +438
   System.Web.Http.Tracing.ITraceWriterExtensions.TraceBeginEndAsync(ITraceWriter traceWriter, HttpRequestMessage request, String category, TraceLevel level, String operatorName, String operationName, Action`1 beginTrace, Func`1 execute, Action`2 endTrace, Action`1 errorTrace) +464
   System.Web.Http.Tracing.Tracers.RequestMessageHandlerTracer.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) +367
   System.Net.Http.DelegatingHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) +41
   System.Web.Http.HttpServer.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) +390
   System.Net.Http.HttpMessageInvoker.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) +55
   System.Web.Http.WebHost.HttpControllerHandler.BeginProcessRequest(HttpContextBase httpContextBase, AsyncCallback callback, Object state) +316
   System.Web.Http.WebHost.HttpControllerHandler.System.Web.IHttpAsyncHandler.BeginProcessRequest(HttpContext httpContext, AsyncCallback callback, Object state) +77
   System.Web.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +301
   System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +155

這因為是 Windows Azure 雲端服務專案發生的情況,所以解決方法有二:

1.

建置 Windows Azure 雲端服務會有兩個專案,一為 ASP.NET MVC 4 Web 應用程式,另一個是 Windows Azure Web Role 專案。必須在方案內設定 Windows Azure Web Role 專案為「啟用」,像我就是設定 ASP.NET MVC 4 Web 應用程式 為「啟用」。

2.

web.config 中移除追蹤接聽項 ( trace listener ):
<trace>
  <listeners>
    <add type="Microsoft.WindowsAzure.Diagnostics.DiagnosticMonitorTraceListener, Microsoft.WindowsAzure.Diagnostics, Version=2.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
      name="AzureDiagnostics">
      <filter type="" />
    </add>
  </listeners>
</trace>  

兩種方法都能解決此種問題,我是用第二種方法解決。


2014年5月15日 星期四

Entity Framework 與 LINQ -- 篩選(Where)使用時機

最近發現,使用Entity Framework的話,要注意下Where的時機,因為有可能是會在SQL端篩選,也有可能會在C#端篩選,可能會導致不同的結果,甚至發生不支援的情況。

拿字串A是否包含了字串B作為範例,這時候在SQL端(還沒產生成實體物件)時執行結果 跟 C#端(已從DB撈出資料)執行結果 就會發生不同的狀況。
先來看一Where下在資料還沒產生成實體物件的時候
C#:
var query = db.Table.Where(x => x.Name.Contains("abc")).ToList();
SQL執行語法會是
SELECT[EmployeeId],[Name]
FROM [dbo].[Employee]
WHERE [Name] LIKE '%abc%'
由此可見,資料判斷是在SQL端處理,然後把結果丟回到C#端。

再來是Where下在ToList()後面
C#:
var query = db.Table.ToList().Where(x => x.Name.Contains("abc"));
SQL執行語法會是
SELECT[EmployeeId],[Name]
FROM [dbo].[Employee]
這時候變成了把所有Employee資料撈出來以後,在篩選資料。

一個是在SQL端下LIKE,一個是在C#端執行Contains,都是包含"abc",但是結果就會不一樣囉。

SQL的LIKE是不分辨大小寫的,所以"ABC"、"AbC"、"abc"、"abC"...都是會被認為是OK的資料。
但是在C#的Contains是有區分大小寫的,所以只有"abc"才會被篩選出來。

如果使用IndexOf去指定不區分大小寫,但是這個只能在C#端使用
錯誤的C#語法:
var query = db.Table.Where(x => x.Name.IndexOf("abc", StringComparison.CurrentCultureIgnoreCase) >= 0).ToList();
這樣會報錯,因為LINQ在轉SQL語法的時候會發生不支援的情況,就回歸到一開始提到的Where時機。
所以我們要在取出資料後,由C#去執行篩選
C#語法:
var query = db.Table.ToList().Where(x => x.Name.IndexOf("abc", StringComparison.CurrentCultureIgnoreCase) >= 0);

PS:Contains是使用IndexOf去實做出來的,所以基本上IndexOf效能會略好於Contains



2014年5月9日 星期五

ASP .NET MVC4 WebApi -- OData 使用 與 實作$inlinecount(續2) -- 中繼傳遞OData參數

延續前二篇
(ASP .NET MVC4 WebApi -- OData 使用 與 實作$inlinecount)
(ASP .NET MVC4 WebApi -- OData 使用 與 實作$inlinecount(續) -- C# Model 接取$inlinecount 資料)

範例為建立一個API,把OData參數傳給資料來源Api,然後做簡單處理或直接回傳(資料來源Api開放使用OData)。

建立一個Api
//如果有$inlinecount就會是不同的格式,所以這邊回傳Object
public Object Get()
{
    return "test";
}
取得所有OData參數
List<string> arrParams = Request.GetQueryNameValuePairs() //所有QueryString的Name、Value集合
    .Where(x => x.Key.StartsWith("$")) //只取得開頭為$
    .Select(x => string.Format("&{0}={1}", x.Key, Uri.EscapeDataString(x.Value))) //組回 &Name=Value
    .ToList();
因為範例的資料來源API本身有指定參數,所以這邊會先把 & 加上,方便後面整理所有參數,請視個人情況調整加上的時機。

判斷是否有設定$inlinecount=allpages
有的話就要用C# Model 接取$inlinecount 資料的共用Model去接資料,沒有就用一般方式即可。
if(arrParams.Select(x => x.ToLower()).Contains("&$inlinecount=allpages") == true)
Select出來轉小寫以後判斷是否包含"&$inlinecount=allpages",因為沒實際測試$filter大小寫是否有區別,所以沒有在取得QueryString的時候就轉。
由組回 Name=Value 時前面有沒有加 & 來決定這邊要不要加。

組合資料來源Api的URL
string.Format("DefauleUrl?name1=val1&name2=val2{0}", string.Join("", arrParams));
string.Join要不要加 & 一樣由前面就決定了,串出來的Url請仔細確認參數部分的格式是不是正確的。

下面是實際範例
public Object Get()
{
    List<string> arrParams = Request.GetQueryNameValuePairs().Where(x => x.Key.StartsWith("$")).Select(x => string.Format("&{0}={1}", x.Key, Uri.EscapeDataString(x.Value))).ToList();
    bool IsAllpages = false;
    string strParams = string.Join("", arrParams);
 string strUrl = string.Format("DefauleUrl?name1=val1&name2=val2{0}", string.Join("", arrParams));
    List<ModelName> Models = new List<ModelName>();
    ODataByApi<ModelName> odateModel = new ODataByApi<ModelName>();
    if (arrParams.Select(x => x.ToLower()).Contains("&$inlinecount=allpages"))
    {
        odateModel = GetData<ODataByApi<ModelName>>(strParams);
        Models = odateModel.Items.ToList();
        IsAllpages = true;
    }
    else
    {
        Models = GetData<List<ModelName>>(strParams);
    }
 //資料處理
    if (Models != null)
    {
        //To do....
    }
 //有$inlinecount=allpages,丟回ODataByAp後return
    if (IsAllpages == true)
    {
        odateModel.Items = Models;
        return odateModel;
    }
 //否則return List
    return Models;
}

參考:
(ASP .NET MVC4 WebApi -- OData 使用 與 實作$inlinecount)
(ASP .NET MVC4 WebApi -- OData 使用 與 實作$inlinecount(續) -- C# Model 接取$inlinecount 資料)