2016年8月24日 星期三

json.net 反序列化繼承的子類別問題

今天遇到 json.net 反序列化的問題,
( json.net deserilize dirven class problem! )

前情提要:

開發 web, server 與 client 透過 json 格式傳遞資料,
server 端在定義 ViewMode 時,DataModel 可能是 ChildAModel 或 ChildBModel 類別,
以 OO多型 ( Polymorphism ) 的設計來說,
實作一個父類 ( BaseModel ) 讓 ChildAModel, ChildBModel 繼承,
這樣 ViewModel.DataModel 就可以指定 ChildAModel, ChildBModel 其中之一的資料格式
如下:
public class BaseModel
{
 public int key { get; set; }
}

public class ChildAModel: BaseModel
{
 public string Name { get; set; }
 public int Age { get; set; }
}

public class ChildBModel : BaseModel
{
 public string SomeProperty1 { get; set; }
 public int SomeProperty2 { get; set; }
}

public class ViewModel
{
 public BaseModel DataModel { get; set; }
}

問題:

依這樣的設計,
將 ViewModel 的資料轉成 JSON 拋給 Client 沒問題,
JavaScript 本身就是延展性高的語言,

但由 Client 再拋給 Server 時,
Json.Net 將 JSON 反序列成物件時,
問題來了!
ViewModel.DataModel 只會被反序列化成 BaseModel ,
其他資料全掉光了,
傷腦筋啦! 該如何是好呢?

Solution:

基本概念是靠實作 CustomCreationConverter.Create 來通知 Json.Net 反序列化 T 時,
要轉成哪一種類別,但此 Create 原生方法卻沒有提供其他資訊來判斷該用哪種類別
example:
public interface IPerson
{
    string FirstName { get; set; }
    string LastName { get; set; }
    DateTime BirthDate { get; set; }
}

public class Employee : IPerson
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public DateTime BirthDate { get; set; }

    public string Department { get; set; }
    public string JobTitle { get; set; }
}

public class PersonConverter : CustomCreationConverter
{
    public override IPerson Create(Type objectType)
    {
        return new Employee();
    }
}

發現有善心人士也遇到此問題,並公開解法
覆寫 JsonConverter,擴充 Create 方法
如下: ( 參考 )
public abstract class JsonCreationConverter : JsonConverter
{
 protected abstract T Create(Type objectType, JObject jsonObject);

 public override bool CanConvert(Type objectType)
 {
  return typeof(T).IsAssignableFrom(objectType);
 }

 public override object ReadJson(JsonReader reader, Type objectType, 
  object existingValue, JsonSerializer serializer)
 {
  var jsonObject = JObject.Load(reader);
  var target = Create(objectType, jsonObject);
  serializer.Populate(jsonObject.CreateReader(), target);
  return target;
 }

 public override void WriteJson(JsonWriter writer, object value, 
  JsonSerializer serializer)
 {
  throw new NotImplementedException();
 }
}

再自訂 Json.Net Conveter,並依物件的 property 回覆類別,如下

public class ModelConverter : JsonCreationConverter
{
 protected override BaseModel Create(Type objectType, 
  JObject jsonObject)
 {
  if (jsonObject["Name"] != null)
  {
   return new ChildAModel();
  }
  else if (jsonObject["SomeProperty1"] != null)
  {
   return new ChildBModel();
  }
  else
  {
   return new BaseModel();
  }
 }
}

最後反序列化的使用方式,如下:

var deserializedModel = JsonConvert.DeserializeObject<BaseModel>(json,
  new ModelConverter ());

以上!

2016年8月3日 星期三

Firefox error: Unable to check input because the pattern is not a valid regexp: invalid identity escape in regular expression


在下大部分時間是用 chrome 開發網頁,
但身為網頁開發人員,
三不五時就要看看自已寫的東西在別的瀏覽器會不會有問題,
今天在 firefox 就中招了,
使用 input pattern attribute 時,
下 regular expression 習慣性會將特殊符號加上 escape ( \ ),
這樣的寫法在 chrome 是沒問題的,如下:

<input type="text" class="form-control" required
   pattern="[\-\_]"
   >

但在 firefox 卻拋出錯誤訊息:
Unable to check <input pattern='[\-\_]'> because the pattern is not a valid regexp: invalid identity escape in regular expression


研究後發現,在 input pattern 不需要做 escape ,在 chrome, firefox 都 work,
修改後,如下:
<input type="text" class="form-control" required
   pattern="[-_]"
   >





2016年7月26日 星期二

The controller for path '/Account/Login' was not found or does not implement IController.


發現一問題,
透過 .net authentication 機制,當未登入狀態,
預設應該導至哪一頁是依 <form loginUrl="..."/> 設定,

<authentication mode="Forms">
  <forms loginUrl="~/login.aspx"/>
</authentication>

但設定後卻無效
查詢發現在 web.config 的 appSettings 加上此行,即可

<add key="loginUrl" value="~/login.aspx" />

原因不明,在此做個紀綠先!

2016年6月28日 星期二

upgrade angular2 new ngForm ( RC3 )

將 angular2 升至 RC2 後,就出現了一段警告訊息:

*It looks like you're using the old forms module. This will be opt-in in the next RC, and will eventually be removed in favor of the new forms module. For more information, see: https://docs.google.com/document/u/1/d/1RIezQqE4aEhBRmArIAS1mRIZtWFf6JxN_7B4meyWK0Y/pub

看來在 RC2 後,ngForm 做了一番修正,需要做一些破壞性的處理,
以下是將舊版 ngForm 升級至新版的步驟:
(目前已升至 RC3 )

1. main.ts 加入:
import { disableDeprecatedForms, provideForms } 
    from '@angular/forms';

bootstrap(AppComponent, [disableDeprecatedForms(), provideForms() ]);

2. component 修改部分:
import {CORE_DIRECTIVES, FORM_DIRECTIVES, NgForm, Control}
    from '@angular/common';
...

@Component({ 
    directives: [FORM_DIRECTIVES]
})
修正為:
import {CORE_DIRECTIVES}    from '@angular/common';
import {REACTIVE_FORM_DIRECTIVES, FormControl, FormGroup}
    from '@angular/forms';
...
@Component({
    directives: [REACTIVE_FORM_DIRECTIVES]
})


3. form class 更名,尤其是有用 FormBuilder 做客製化驗證的部分:

REACTIVE_FORM_DIRECTIVES:
  • formGroup (deprecated: ngFormModel) 
  • formControl (deprecated: ngFormControl) 
  • formControlName (deprecated: ngControl) 
  • formGroupName (deprecated: ngControlGroup) 
  • formArrayName (deprecated: ngControlGroup) 
  • FormControl() (deprecated: Control) 
  • FormGroup() (deprecated: ControlGroup) 
  • FormArray() (deprecated: ControlArray) 
  • FormBuilder (same) 
  • Validators (same)

4. form control ( input, select...),需加上 name attribute,
#name="ngForm" 要改為 #name="ngModel":
<input type="text" class="form-control" required
                           [(ngModel)]="name"
                           ngControl="name"
                           #name="ngForm" />
修改為:
<input type="text" class="form-control" required
                           [(ngModel)]="name"
                           ngControl="name"
                           name="name"
                           #name="ngModel" />

5. ngFormModel, ngControl 修改部分 ( HTML ):
<form #templateForm="ngForm" 
          [ngFormModel]="_form" />
    <input type="text" class="form-control" required
                           [(ngModel)]="TemplateName"
                           pattern="^[a-zA-Z0-9_.\-\u4e00-\u9fa5-\(-\)\s]+$"
                           ngControl="TemplateName" />

</form>
修改為:
<form #templateForm="ngForm" 
          [formGroup]="_form" />
    <input type="text" class="form-control" required
                           name="TemplateName"
                           [(ngModel)]="TemplateName"
                           pattern="^[a-zA-Z0-9_.\-\u4e00-\u9fa5-\(-\)\s]+$"
                           formControlName="TemplateName" />

</form>

6. ngFormModel, ngControl 修改部分 ( typescript)::
_form: ControlGroup;
constructor(
        @Inject(FormBuilder) fb: FormBuilder
    ) {
        this._addTemplateForm = fb.group({
            TemplateName: ['', Validators.required]
        })
    }
修改為:
_form: FormGroup;
constructor(
        @Inject(FormBuilder) fb: FormBuilder
    ) {
        this._addTemplateForm = fb.group({
            TemplateName: ['', Validators.required]
        })
    }


升級步驟大致如上,希望可以幫助到各位囉!

2016年6月18日 星期六

Arduino - 重量感測器(電子秤)

前一陣子玩了一下 Arduino 測量重量的 sensor,也就是電子秤的原理,
電子秤的原理就是根據金屬受力以後的微弱形變來計算其受力的大小。

透過稱重專用的AD晶片 ( HX711 ) 測量出當前材料所受到拉彎扭的力,
並結合 Arduino 和對應 Library 即可。

HX711晶片:

重量感測器結構:





重量感測器結合木板:





實際操作畫面:(畫面是用較差的木板測試,後來才改用較適合的木板)

2016年6月12日 星期日

使用 jQuery 操作 XML

寫 web 多年,在實作前端 ( javascript ) 與 Server 的溝通總是以 JSON 為主,
從沒想過用 XML 格式進行溝通,
一直覺得在前端 Javascript 操作 XML 是一件痛苦的事,
原來在 jQuery 1.5 就有提供方便的方式查詢與操作 XML,

使用 $($.parseXML(xmlStr)) 轉成 jQuery 物件後,
即可像操作 dom 一樣,瀏覽 xml 囉!
範例如下:(參考官網)

var xml = "RSS Title",
  xmlDoc = $.parseXML( xml ),
  $xml = $( xmlDoc ),
  $title = $xml.find( "title" );
 
// Append "RSS Title" to #someElement
$( "#someElement" ).append( $title.text() );
 
// Change the title to "XML Title"
$title.text( "XML Title" );
 
// Append "XML Title" to #anotherElement
$( "#anotherElement" ).append( $title.text() );

2016年6月8日 星期三

unable to read data from the transport connection an existing connection was forcibly closed by remote host

今天遇到一案例,
使用 WebClient 下載檔案,
卻拋出以下錯誤訊息:
unable to read data from the transport connection an existing connection was forcibly closed by remote host

解決方式:
在建立 WebClient 前,加入此行,即可
System.Net.ServicePointManager.Expect100Continue = false;

那什麼要將 Expect100Continue 設為 false 呢?
因為 Server 跟 Client 在溝通時,中途會經過很多節點,如 proxy,
但這些節點不一定支援 Expect100Continue 進而拋出此 exception

那 Expect100Continue  又是什麼?
簡單來說是為了減少網路流量所設計的機制,原因如下:( 參考 msdn )

For example, assume the Expect100Continue property is false.When the request is sent to the server, it includes the data.If, after reading the request headers, the server requires authentication and must send a 401 response, the client must resend the data with proper authentication headers.

If this property is true, the request headers are sent to the server.If the server has not rejected the request, it sends a 100-Continue response signaling that the data can be transmitted.If, as in the preceding example, the server requires authentication, it sends the 401 response and the client has not unnecessarily transmitted the data.