2011年12月9日

[WCF]Invoke <enableWebScript> WCF Service

有一個已經在production上的WCF Service, 它的設定檔如下

<services>
<service behaviorConfiguration="defaultServiceBehavior" name="Trend.BPM.AppWeb.Services.AjaxPipe">
<endpoint address="" behaviorConfiguration="Trend.BPM.AppWeb.Services.AjaxPipeAspNetAjaxBehavior"
binding="webHttpBinding" bindingConfiguration="webHttpBinding"
name="ajaxPipeEndpoint" contract="Trend.BPM.AppWeb.Services.AjaxPipe">
<identity>
<dns value="localhost" />
</identity>
</endpoint>
</service>
</services>
<bindings>
<webHttpBinding>
<binding name="webHttpBinding" allowCookies="true" bypassProxyOnLocal="true"
maxBufferPoolSize="524288000" maxReceivedMessageSize="65536000">
<readerQuotas maxDepth="32" maxStringContentLength="8192000"
maxArrayLength="16384000" maxBytesPerRead="409600000" maxNameTableCharCount="16384000" />
<security mode="TransportCredentialOnly">
<transport clientCredentialType="Windows" />
</security>
</binding>
</webHttpBinding>
</bindings>



<behaviors>
<endpointBehaviors>
<behavior name="Trend.BPM.AppWeb.Services.AjaxPipeAspNetAjaxBehavior">
<enableWebScript />
</behavior>
</endpointBehaviors>
</behaviors>



程式碼



[ServiceContract(Namespace = "")]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
[ServiceBehavior(AddressFilterMode = AddressFilterMode.Any)]
public class AjaxPipe {
[OperationContract]
public string ProcessForm(string type, string action, string content, string workItemData, string clientTimeZone) {
//...
}


這個service原本主要是當成jQuery AJAX的呼叫使用, 但是有一個狀況需要在後端使用C#呼叫.


原本我用非常直觀的方式, 產生一個proxy, 並使用ChannelFactory呼叫.


//Create Binding
protected BasicHttpBinding CreateDefaultBinding(BasicHttpSecurityMode securityMode) {
BasicHttpBinding binding = new BasicHttpBinding(securityMode);
binding.Security.Transport = new HttpTransportSecurity {
ClientCredentialType = HttpClientCredentialType.Windows
};

return binding;
}

//Create Channel
protected T GetServiceChannel<T>(string configurationSection, string url) {
BasicHttpBinding binding = CreateDefaultBinding();
EndpointAddress endpoint = null;
if (string.IsNullOrEmpty(url)) {
endpoint = new EndpointAddress(GetServiceURL(configurationSection));
}
else {
endpoint = new EndpointAddress(url);
}
return new ChannelFactory<T>(binding, endpoint).CreateChannel();
}

//Invoke
var svc = GetServiceChannel<AJAX.AjaxPipe>(config,url);
svc.ProcessForm("Test","Submit",null,null,null);



顯然這個方式會失敗, 因為Server是使用WebHttpBinding, 而不是BasicHttpBinging, 因此我修改我的程式為使用WebHTtpBinging


public AJAXPIPE.AjaxPipe GetWebAJAXPipe(string url) {

if (string.IsNullOrEmpty(url))
throw new ArgumentNullException("URL");

var binding = new WebHttpBinding(WebHttpSecurityMode.TransportCredentialOnly);
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Windows;
ChannelFactory<AJAXPIPE.AjaxPipe> cf = new ChannelFactory<AJAXPIPE.AjaxPipe>(binding, url);
var behavior = new WebHttpBehavior();
cf.Endpoint.Behaviors.Add(behavior);
return cf.CreateChannel();
}


看起來已經使用了WebHttpBinding, Secutiry Mode也都符合了Server設定, 但是呼叫時會得到這個錯誤


Operation 'ProcessForm' of contract 'AjaxPipe' specifies multiple request

body parameters to be serialized without any wrapper elements.


At most one body parameter can be serialized without wrapper elements.


Either remove the extra body parameters or set the BodyStyle property


on the WebGetAttribute/WebInvokeAttribute to Wrapped.



Google的結果, 大多是說要修改Server的config, 使用<webHttp>而不是使用<enableWebScript>, 但是因為這個Service已經在production, 修改這裡可能需要一些測試驗證, 因此我比較想要在不更改Server的狀況下達到呼叫的目的.



於是, 將程式碼修改如下



public AJAXPIPE.AjaxPipe GetWebAJAXPipe(string url) {
if (string.IsNullOrEmpty(url))
throw new ArgumentNullException("URL");
var binding = new WebHttpBinding(WebHttpSecurityMode.TransportCredentialOnly);
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Windows;
ChannelFactory<AJAXPIPE.AjaxPipe> cf = new ChannelFactory<AJAXPIPE.AjaxPipe>(binding, url);
var behavior = new WebHttpBehavior() {
DefaultBodyStyle = WebMessageBodyStyle.Wrapped,
FaultExceptionEnabled = true, //for debug
HelpEnabled = true //for debug
};

cf.Endpoint.Behaviors.Add(behavior);
return cf.CreateChannel();
}



雖然結果只有一行, 但是我只想說…終於打完收工了…

2011年11月22日

[SQL]A connection was successfully established with the server, but then an error occurred during the login process

While try to connect to SQL server, I keep getting this error.

Solution is simple but wired, details please refer to William Vaughn’s blog

There are two solutions, I took the simplest one – when exception thrown, try to open connection again.

using (conn = CreateConnection()) {
try {
conn.Open();
}
catch {
/* http://betav.com/blog/billva/2008/11/solution-forcibly-closed-sql-s.html#more */
conn.Dispose();
conn = CreateConnection();
conn.Open();
}
//...
}

2011年11月11日

[WP7學習筆記] DataBinding (2)

繼續(1)的例子, 首先把ListBox的ItemTemplate跟Style一起做成Resource

<Style x:Key="Style_RadioButtonListItem" TargetType="ListBoxItem">
            <Setter Property="Padding" Value="3" />
            <Setter Property="VerticalContentAlignment" Value="Top" />
            <Setter Property="Background" Value="Red" />
            <Setter Property="Foreground" Value="Blue" />
            <Setter Property="BorderThickness" Value="1"/>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="ListBoxItem">
                        <Grid Background="Yellow">
                            <RadioButton IsChecked="{Binding Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}, Path=IsSelected}"/>
                            <TextBlock Foreground="{Binding Foreground, RelativeSource={RelativeSource TemplatedParent}}" TextWrapping="Wrap" Text="{Binding DataContext.CategoryDesc, RelativeSource={RelativeSource TemplatedParent}}"></TextBlock>
                        </Grid>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>

其中這一段


<TextBlock Foreground="{Binding Foreground, RelativeSource={RelativeSource TemplatedParent}}" TextWrapping="Wrap" Text="{Binding DataContext.CategoryDesc, RelativeSource={RelativeSource TemplatedParent}}"></TextBlock>


因為這個TextBox是ListBoxItem Template的一部分, 他的資料來源是他的TemplatedParent, 也就是ListBox, 因此指定RelativeSource={RelativeSource TemplatedParent}.


另外因為我希望每個ListBoxItem都顯示出選項的說明, 因此指定Text屬性Bind到DateContext (在我的例子裡, 是一個Category物件)的CategoryDesc屬性.
ListBox則改寫為

<ListBox x:Name="_Categories" Grid.Row="1" Background="Blue"
                 SelectedItem="{Binding SelectedCollectionItem,Mode=TwoWay}"
                 ItemContainerStyle="{StaticResource Style_RadioButtonListItem}">

以程式指定ListBox的Items資料來源以及預設的選項

var categories = Data.DAL.Instance.QueryCategories();
_Categories.ItemsSource = categories;
_Categories.SelectedItem = _Categories.Items.First();//接下來應該有辦法可以讓這一段也用DataBinding的方式做才對…
 



結果如下…只能說,有沒有美感真的是差很多啊…XD


sp3

2011年11月10日

[WP7學習筆記] DataBinding (1)

假如我有一個資料結構是這樣    


public class TransactionRecord
{
public string CategoryID
{
get;set;
}
public string CategoryDesc
{
get;set;
}


public int Amount
{
get;set;
}
}

程式會透過資料存取取得所有的Category並顯示在ListBox中.


先定義主畫面


<phone:PhoneApplicationPage>
<!—省略—>


    <Grid x:Name="LayoutRoot" Background="Transparent">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
<TextBlock x:Name="ApplicationTitle" Text="MY APPLICATION" Style="{StaticResource PhoneTextNormalStyle}"/>
<TextBlock x:Name="PageTitle" Text="page name" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
</StackPanel>
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<ScrollViewer>
<ItemsControl x:Name="_List">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border x:Name="_Border" x:FieldModifier="Private">
<!—省略—>
<Grid Tag="{Binding}" Height="120">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Foreground="Black" Grid.Column="0" Text="{Binding CategoryDesc}"/>
<Grid Grid.Column="1">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Foreground="Black" Text="{Binding CategoryID}"/>
<TextBlock Grid.Row="1" Foreground="Black" Text="{Binding Amount}"/>
</Grid>
</Grid>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</Grid>
</Grid>
</phone:PhoneApplicationPage>



其中的紅色粗體字型的地方即為我想要顯示的相對應的資料, 對應到資料結構的屬性. 然後在程式中指定



var records = Data.DAL.Instance.QueryRecords();
_List.ItemsSource = records;


結果


sp



如果我希望可以再畫面上修改金額, 並且直接把修改後的結果更心回資料集. 最簡單的做法是, 將顯示用的TextBlack改為TextBox, 並指定Binding Mode為Twoway如下



<TextBox Grid.Row="1" Height="84" Foreground="Black" InputScope="Number" Text="{Binding Amount, Mode=TwoWay}"/>


這時, 畫面上可以直接修改資料, 修改完後和下按鈕, 可以發現資料已經被修改了


private void button1_Click(object sender, RoutedEventArgs e)
{
var item = _List.Items[0] as Data.TransactionRecord;
MessageBox.Show(item.Amount.ToString());
}


sp2


這樣的方式, 資料是在離開TextBox時修改, 也就是Lost Focus時. 如果我希望的是在修改資料的當下就修改資料來源呢 ?


<TextBox Grid.Row="1" Height="84" Foreground="Black" InputScope="Number" Text="{Binding Amount, Mode=TwoWay,UpdateSourceTrigger=Explicit}" TextChanged="TextBox_TextChanged"/>



加入UpdateSourceTrigger=Explicit, 並指定TextChanged的Event Handler



        private void TextBox_TextChanged(object sender, TextChangedEventArgs e)
{
BindingExpression exp = (sender as TextBox).GetBindingExpression(TextBox.TextProperty);
exp.UpdateSource();
PageTitle.Text = "Amount=" + (_List.Items[0] as TransactionRecord).Amount.ToString();
}


修改的結果會立刻反應到資料來源

2011年11月1日

[WP7]取得組件中的XML檔案內容

首先,把檔案的build action設為Resource

image

使用Application.GetResourceStream()取得內容

var ri = Application.GetResourceStream(new Uri("MyDatabase.Demo;component/InitData/InitData.xml", UriKind.Relative));
if(ri != null){
   using(var sr = new System.IO.StreamReader(ri.Stream)){
      var xml = sr.ReadToEnd();
   }
}

[NoSQL]Sterling Database

最近在survey之後案子也許會用到的NoSQL Solution, 這是目前在CodePlex上滿受歡迎的一個解決方案. Sterling NoSQL OODB for .Net

首先, 撰寫自己的Sterling Service如下

using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using Wintellect.Sterling.Database;
using Wintellect.Sterling;
namespace MoneyBook.Data.DBHelpers
{
    public class MBookDBService : IApplicationService, IApplicationLifetimeAware, IDisposable
    {
        private SterlingEngine _engine = null;
        private SterlingDefaultLogger _logger = null;
        public ISterlingDatabaseInstance Database { get; private set; }
        #region IApplicationService Members
        public void StartService(ApplicationServiceContext context)
        {
            if (System.ComponentModel.DesignerProperties.IsInDesignTool)
                return;
            if (_engine != null)
            {
                StopService();
                Dispose();
            }
            _engine = new SterlingEngine();
            
        }
        public void StopService()
        {
            
        }
        #endregion
        #region IApplicationLifetimeAware Members
        public void Exited()
        {
            Dispose();
        }
        public void Exiting()
        {
            if (System.ComponentModel.DesignerProperties.IsInDesignTool)
            {
                return;
            }
            if (System.Diagnostics.Debugger.IsAttached && _logger != null)
            {
                _logger.Detach();
            }
        }
        public void Started()
        {
        }
//remove first lunch flag
        private static void DeleteFirstLunchFlag()
        {
            using (System.IO.IsolatedStorage.IsolatedStorageFile file = System.IO.IsolatedStorage.IsolatedStorageFile.GetUserStoreForApplication())
            {
                if (file.FileExists("/MBook/lunched.txt"))
                {
                    file.DeleteFile("/MBook/lunched.txt");
                }
            }
        }
//Check if this is the first time we lunching this database instance
        private static bool IsFirstLunch()
        {
            using (System.IO.IsolatedStorage.IsolatedStorageFile file = System.IO.IsolatedStorage.IsolatedStorageFile.GetUserStoreForApplication())
            {
                return file.FileExists("/MBook/lunched.txt");
            }
        }
//Set first lunch flag
        private static void SetFirstLunchFlag()
        {
            using (System.IO.IsolatedStorage.IsolatedStorageFile file = System.IO.IsolatedStorage.IsolatedStorageFile.GetUserStoreForApplication())
            {
                if (!file.FileExists("/MBook/lunched.txt"))
                {
                    try
                    {
                        file.CreateFile("/MBook/lunched.txt").Write(new byte[] { 0x31 }, 0, 1);
                    }
                    catch { }
                }
            }
        }
        public void Starting()
        {
            if (System.ComponentModel.DesignerProperties.IsInDesignTool)
                return;
            _engine.Activate();
            Database = _engine.SterlingDatabase.RegisterDatabase<MBookDatabase>();
//Check if this is the first time, if so, setup initial data
//--NOTE-- this is only for testing purpose
            if (IsFirstLunch())
            {
                var db = (Database as MoneyBook.Data.DBHelpers.MBookDatabase);
                var cs = db.GetInitCategories();
                foreach (var c in cs)
                {
                    Database.Save(c);
                }
                SetFirstLunchFlag();
            }
        }
        #endregion
        #region IDisposable Members
        public void Dispose()
        {
            if (_engine != null)
            {
                _engine.Dispose();
                _engine = null;
            }
//Also, delete the flag, this is only for testing purpose
            DeleteFirstLunchFlag();
            GC.SuppressFinalize(this);
        }
        #endregion
    }
}


然後撰寫自己的Database



public partial class MyDatabase: BaseDatabaseInstance
    {
//override this method to create our own table definations
        protected override System.Collections.Generic.List<ITableDefinition> RegisterTables()
        {
            System.Collections.Generic.List<ITableDefinition> tables = new System.Collections.Generic.List<ITableDefinition>();
            //Add book table
            tables.Add(CreateTableDefinition<MBook, string>(x => x.BookID));
            //Add record table
            tables.Add(CreateTableDefinition<MRecord, string>(x => x.ID).WithIndex<MRecord, DateTime, string>("Index_MRecord_Date", x => x.Date));
            //Add category table
            tables.Add(CreateTableDefinition<MCategory, string>(x => x.CategoryID));
            //Add subcategory table
            tables.Add(CreateTableDefinition<MSubcategory, string>(x => x.SubcategoryID));
            return tables;
        }


使用前必須在App.xml中註冊為ApplicationLifetimeObject



<Application 
    x:Class="MoneyBook.UI.App"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"       
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
    xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
    xmlns:MBook="clr-namespace:MyDatabasae.Data.DBHelpers;assembly=MyDatabase.Data"
    xmlns:Sterling="clr-namespace:Wintellect.Sterling;assembly=Wintellect.Sterling.WindowsPhone">
    <!--Application Resources-->
    <Application.Resources>
    </Application.Resources>
    <Application.ApplicationLifetimeObjects>
        <!--Required object that handles lifetime events for the application-->
        <shell:PhoneApplicationService 
            Launching="Application_Launching" Closing="Application_Closing" 
            Activated="Application_Activated" Deactivated="Application_Deactivated"/>
        <MBook:MBookDBService/><!--Here-->
    </Application.ApplicationLifetimeObjects>
<!---->
</Application>


查詢資料



return MDBInstance.Query<MyDatabase.MCategory, string>().Select(x => x.LazyValue.Value).ToList();

2011年10月22日

[WP7]繼承自ListBox的自訂控制項

要撰寫一個繼承自ListBoxItem的自訂控制項, 照著書上說的把base class改成ListBoxItem, 但是在compile時出現*.g.i.cs錯誤. 其實, 除了在.cs中指定之外, 還需要修改XAML宣告如下. 把Root element的名稱改成被繼承的控制項名稱即可.

<ListBoxItem x:Class="Test.UI.Controls.TestItem"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    FontFamily="{StaticResource PhoneFontFamilyNormal}"
    FontSize="{StaticResource PhoneFontSizeNormal}"
    Foreground="{StaticResource PhoneForegroundBrush}"
    Background="{StaticResource PhoneBackgroundBrush}"
    d:DesignHeight="32" d:DesignWidth="480">
    <!—其他宣告—>
</ListBoxItem>

2011年10月18日

[EF4]Member Mapping is not specified. (How to change mapping type in an entity)

I got this error when modifying a property type of an entity from Int32 to Int64 in visual studio entity designer.

image

Solution : open .edmx in XML editor, find the property you want to change and specified desired data type in XML.

  1: <Property Name="REMOVE_QTY" Type="bigint" />

[EF4]New transaction is not allowed because there are other threads running in the session

I got “New transaction is not allowed because there are other threads running in the session”while trying to delete some records via EF4.

The code looks like the following.

  1: using (var db = new MyDB()) {
  2: 	var ds = db.MyTable.Where(x => x.EndTime.Value <= DateTime.UtcNow);
  3: 	foreach (var d in ds) {
  4: 	    db.MyTable.DeleteObject(d);
  5: 	    db.SaveChanges();
  6: 	}
  7: }


One thing we need to know about Entity Framework is that when you iterate results in a loop such as “foreach”, it actually creates a open reader to iterate data for you. So back to the codes, since we are still drawing data via an active reader, then obviously we cannot “SaveChanges” when the connection is still in use. What we need to do here is to force EF to return all records, after that, we can do the “SaveChanges” call. So simply force to retrieve all data via invoking “ToList” on the entity.



  1: using (var db = new MyDB()) {
  2: 	var ds = db.MyTable.Where(x => x.EndTime.Value <= DateTime.UtcNow).ToList();
  3: 	foreach (var d in ds) {
  4: 	    db.MyTable.DeleteObject(d);
  5: 	    db.SaveChanges();
  6: 	}
  7: }

2011年9月22日

[WCF] Passing & Retrieving message header in basicHttpBinding Services

To send message properties to the server.


  1: var svc = new GPServiceFactory().GetInboxService();
  2: using (new OperationContextScope(((IClientChannel)svc))) {
  3:  OperationContext oc = OperationContext.Current;
  4:  HttpRequestMessageProperty httpRequestProperty = null;
  5:   if (oc.OutgoingMessageProperties.ContainsKey(HttpRequestMessageProperty.Name)) {
  6:   httpRequestProperty =
  7:    oc.OutgoingMessageProperties[HttpRequestMessageProperty.Name]
  8:      as HttpRequestMessageProperty;
  9:    }
 10: 
 11:    if (httpRequestProperty == null) {
 12:     httpRequestProperty = new HttpRequestMessageProperty();        
 13:    }
 14:    httpRequestProperty.Headers.Add("ClientTimeZone", param["TimeZone"]);
 15:    oc.OutgoingMessageProperties[HttpRequestMessageProperty.Name] = httpRequestProperty;
 16:    return svc.GetWorkitemsInQueue(appId, UserId, queueName, 1000, 1);
 17: }

 


To retrieve properties

  1: Dictionary<String, String> param = null;
  2: try {
  3:  if (OperationContext.Current.IncomingMessageProperties.Keys != null) {
  4:   param = new Dictionary<string, string>();
  5:    foreach (var key in (OperationContext.Current.IncomingMessageProperties.Keys)) {
  6:     if (System.ServiceModel.OperationContext.Current.IncomingMessageProperties[key] is HttpRequestMessageProperty) {
  7:      var p = OperationContext.Current.IncomingMessageProperties[key] as HttpRequestMessageProperty;
  8:       foreach (string k in p.Headers.AllKeys) {
  9:        if (k.Equals("timezone", StringComparison.OrdinalIgnoreCase)) {
 10:         string v = p.Headers[k].StartsWith("+") ? p.Headers[k].Substring(1) : p.Headers[k];
 11:         param.Add("TimeZone", v);
 12:        }
 13:        else {
 14:         param.Add(k, p.Headers[k]);
 15:        }
 16:      }
 17:    }
 18:  }
 19: }

Blog Archive

About Me