When you tried to deserialize the binary to an object but you encountered the following exception:
BinaryFormatter.Deserialize “unable to find assembly”
Basically, it tells you that it cannot find the DLL by version + name. This is commonly issue when you change the DLL version number or move the class to another project/assembly. As a result, we need a way to tell the BinaryFormatter class what is the correct new "type" for the binary.
In the deserialization process, you need to add a line to use your custom binder:
using (MemoryStream memory = new MemoryStream(user_input))
{
BinaryFormatter binary = new BinaryFormatter();
//fix the deserialization error when the DLL version has been changed.
binary.Binder = new PreMergeToMergedDeserializationBinder();
// convert the binary to the list.
this._data = binary.Deserialize(memory) as List<CUserDataItem>;
}
And then add the following class. I have enhanced this class and it is able to handle the generic list as well.
public sealed class PreMergeToMergedDeserializationBinder : System.Runtime.Serialization.SerializationBinder
{
public override Type BindToType(string assemblyName, string typeName)
{
Type typeToDeserialize = null;
// For each assemblyName/typeName that you want to deserialize to
// a different type, set typeToDeserialize to the desired type.
String exeAssembly = Assembly.GetExecutingAssembly().FullName;
// The following line of code returns the type.
// extract the 'old dll name/version'.
string old_dll = typeName.ExtractString(',', ']');
if (old_dll.IsNotEmpty())
{
// for generic list, we replace the dll name/version here.
typeToDeserialize = Type.GetType(typeName.Replace(old_dll, exeAssembly.ToString()));
}
else
{
// for 1 single object, the 'typeName' is the class name.
// We should return the type name with the new dll name/version.
typeToDeserialize = Type.GetType(String.Format("{0}, {1}",
typeName, exeAssembly));
}
System.Diagnostics.Debug.Assert(typeToDeserialize != null);
return typeToDeserialize;
}
}
I have an string class extension which helps to extract partial string:
public static string ExtractString(this string s,
char start_char,
char end_char)
{
int i = s.IndexOf(start_char);
if (i >= 0)
{
//16.Nov.2011-lhw-the 'end_char' should be search after the 'start_char'.
int i2 = s.IndexOf(end_char,
i + 1); //16.Nov.2011-lhw-missing the start pos!!
string tmp = s.Substring(i + 1,
i2 - i - 1);
return tmp;
}
else
{
return string.Empty;
}
}
Reference:
http://stackoverflow.com/questions/5170333/binaryformatter-deserialize-unable-to-find-assembly-after-ilmerge
Saturday, August 1, 2015
Thursday, July 2, 2015
Routing to ASHX
Here is the piece of code that I found in CodeProject.com. By adding this extention method, you will be able to route the request to ASHX:
namespace System.Web.Routing
{
public class HttpHandlerRoute : IRouteHandler
{
private String _virtualPath = null;
private IHttpHandler _handler = null;
public HttpHandlerRoute(String virtualPath)
{
_virtualPath = virtualPath;
}
public HttpHandlerRoute(IHttpHandler handler)
{
_handler = handler;
}
public IHttpHandler GetHttpHandler(RequestContext requestContext)
{
IHttpHandler result;
if (_handler == null)
{
result = (IHttpHandler)System.Web.Compilation.BuildManager.CreateInstanceFromVirtualPath(_virtualPath, typeof(IHttpHandler));
}
else
{
result = _handler;
}
return result;
}
}
public static class RoutingExtensions
{
public static void MapHttpHandlerRoute(this RouteCollection routes, string routeName, string routeUrl, string physicalFile, RouteValueDictionary defaults = null, RouteValueDictionary constraints = null)
{
var route = new Route(routeUrl, defaults, constraints, new HttpHandlerRoute(physicalFile));
RouteTable.Routes.Add(routeName, route);
}
public static void MapHttpHandlerRoute(this RouteCollection routes, string routeName, string routeUrl, IHttpHandler handler, RouteValueDictionary defaults = null, RouteValueDictionary constraints = null)
{
var route = new Route(routeUrl, defaults, constraints, new HttpHandlerRoute(handler));
RouteTable.Routes.Add(routeName, route);
}
}
}
To access the routing data in ASHX, you need to do this:
var o = context.Request.RequestContext.RouteData.Values["id"];
if (o != null)
{
q = o.ToString();
}
Reference:
http://www.codeproject.com/Tips/272258/ASP-net-HttpHandler-Routing-Support
Posting data in JSON format to the ASP.NET website using WinForm
Previously, we have shown how to post JSON data using JQuery to ASP.NET.
http://laucsharp.blogspot.com/2013/03/posting-data-in-json-format-to-aspnet.html
Now, we are going to post JSON data using WinForm:
This is our business object which will reside at the server and client.
public class Class1
{
public string code { get; set; }
public string name { get; set; }
public override string ToString()
{
return string.Format("code={0}, name={1}",
this.code,
this.name);
}
}
In the WinForm client program, when the user hit Button1 after keyed in the client code and name, the data will be submitted to the server:
private void button1_Click(object sender, EventArgs e)
{
// store the user input into the business object.
Class1 data = new Class1();
data.code = this.client_code.Text;
data.name = this.client_name.Text;
// convert it into json format.
JavaScriptSerializer js = new JavaScriptSerializer();
string json_data = js.Serialize(data);
// create the web request.
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create("http://localhost:57655/dataGateway.ashx");
request.ContentType = "application/json;";
request.Method = "POST";
// write the json data into the request stream.
using (StreamWriter writer = new StreamWriter(request.GetRequestStream()))
{
writer.Write(json_data);
}
// get the server response.
using (WebResponse response = request.GetResponse())
{
// read the server response.
Stream response_stream = response.GetResponseStream();
using (StreamReader r = new StreamReader(response_stream))
{
// do what ever you want with the response.
this.label5.Text = r.ReadToEnd();
}
}
}
Finally, at the server side, we add a Generic Handler (dataGateway.ashx) and it looks like this:
<%@ WebHandler Language="C#" Class="dataGateway" %>
using System;
using System.Web;
using System.IO;
using System.Web.Script.Serialization;
public class dataGateway : IHttpHandler
{
public void ProcessRequest(HttpContext context)
{
context.Response.ContentType = "text/plain";
string s;
// get the contents from the request stream
Stream stream = context.Request.InputStream;
using (StreamReader r = new StreamReader(stream))
{
s = r.ReadToEnd();
}
// ensure that the content is not empty.
if (string.IsNullOrEmpty(s) || s.Length == 0)
{
context.Response.Write("'data' cannot be blank");
return;
}
// convert it from json format to our business object
JavaScriptSerializer js = new JavaScriptSerializer();
Class1 obj = js.Deserialize<Class1>(s);
// do whatever you want
context.Cache["data"] = obj;
// returns the response code/status to the caller.
context.Response.Write("ok. received the data =>" + s);
}
public bool IsReusable { get { return false; } }
}
Next, sending compressed data in WinForm:
http://laucsharp.blogspot.my/2018/04/posting-compressed-data-in-json-format.html
http://laucsharp.blogspot.com/2013/03/posting-data-in-json-format-to-aspnet.html
Now, we are going to post JSON data using WinForm:
This is our business object which will reside at the server and client.
public class Class1
{
public string code { get; set; }
public string name { get; set; }
public override string ToString()
{
return string.Format("code={0}, name={1}",
this.code,
this.name);
}
}
In the WinForm client program, when the user hit Button1 after keyed in the client code and name, the data will be submitted to the server:
private void button1_Click(object sender, EventArgs e)
{
// store the user input into the business object.
Class1 data = new Class1();
data.code = this.client_code.Text;
data.name = this.client_name.Text;
// convert it into json format.
JavaScriptSerializer js = new JavaScriptSerializer();
string json_data = js.Serialize(data);
// create the web request.
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create("http://localhost:57655/dataGateway.ashx");
request.ContentType = "application/json;";
request.Method = "POST";
// write the json data into the request stream.
using (StreamWriter writer = new StreamWriter(request.GetRequestStream()))
{
writer.Write(json_data);
}
// get the server response.
using (WebResponse response = request.GetResponse())
{
// read the server response.
Stream response_stream = response.GetResponseStream();
using (StreamReader r = new StreamReader(response_stream))
{
// do what ever you want with the response.
this.label5.Text = r.ReadToEnd();
}
}
}
Finally, at the server side, we add a Generic Handler (dataGateway.ashx) and it looks like this:
<%@ WebHandler Language="C#" Class="dataGateway" %>
using System;
using System.Web;
using System.IO;
using System.Web.Script.Serialization;
public class dataGateway : IHttpHandler
{
public void ProcessRequest(HttpContext context)
{
context.Response.ContentType = "text/plain";
string s;
// get the contents from the request stream
Stream stream = context.Request.InputStream;
using (StreamReader r = new StreamReader(stream))
{
s = r.ReadToEnd();
}
// ensure that the content is not empty.
if (string.IsNullOrEmpty(s) || s.Length == 0)
{
context.Response.Write("'data' cannot be blank");
return;
}
// convert it from json format to our business object
JavaScriptSerializer js = new JavaScriptSerializer();
Class1 obj = js.Deserialize<Class1>(s);
// do whatever you want
context.Cache["data"] = obj;
// returns the response code/status to the caller.
context.Response.Write("ok. received the data =>" + s);
}
public bool IsReusable { get { return false; } }
}
Next, sending compressed data in WinForm:
http://laucsharp.blogspot.my/2018/04/posting-compressed-data-in-json-format.html
Thursday, June 18, 2015
Excluding folders upon publishing the website
In VS2013, after you have setup the "publish" (right click on the website and choose Publish Web Site), a new configuration file (website.publishproj) will be added to the project.
To exclude the folders, you have to open this file and add the following section with the "project" section:
<ItemGroup>
<ExcludeFromPackageFolders Include="log;temp;">
<FromTarget>Remove temp folders</FromTarget>
</ExcludeFromPackageFolders>
</ItemGroup>
In the "Include" attribute, it contains the folder to be removed when publishing the website. The above example excluding "log" and "temp" folders.
To exclude the folders, you have to open this file and add the following section with the "project" section:
<ItemGroup>
<ExcludeFromPackageFolders Include="log;temp;">
<FromTarget>Remove temp folders</FromTarget>
</ExcludeFromPackageFolders>
</ItemGroup>
In the "Include" attribute, it contains the folder to be removed when publishing the website. The above example excluding "log" and "temp" folders.
Sunday, June 14, 2015
Asp.net WebForm + Routing
To add the routing support in the WebForm, you need to add "System.Web.Routing" reference to your website.
Then, in the global.asax file, add the following codes:
void Application_Start(object sender, EventArgs e)
{
// Code that runs on application startup
RegisterRoute(System.Web.Routing.RouteTable.Routes);
}
void RegisterRoute(System.Web.Routing.RouteCollection r)
{
// shows all customer
r.MapPageRoute("allcust",
"customer",
"~/customer.aspx");
// shows 1 customer profile
r.MapPageRoute("cust",
"customer/{code}",
"~/customer.aspx");
// shows the doc for the customer
r.MapPageRoute("cust_doc",
"customer/{code}/{doc_no}",
"~/customer.aspx");
// this allows any sub-level of parameters and the page requires to parse 'Page.RouteData.Values["queryvalues"]' (only 1 value).
// The caller can call this route by the following url:
// /cust
// /cust/a001
// /cust/a001/inv123456
//
r.MapPageRoute("cust_query",
"cust/{*queryvalues}",
"~/customer.aspx");
}
Finally, add all the aspx page that you need to display the information. In our example, we have only one page that is customer.aspx. You can get the parameter values in "Page.RouteData". Note: the last route that has been setup in the above was not shown in the following codes.
protected void Page_Load(object sender, EventArgs e)
{
if (this.RouteData.Values.Count == 0)
{
this.lbl1.Text = "cust list";
}
else
{
if (this.RouteData.Values.Count == 1)
{
this.lbl1.Text = "customer => " + this.RouteData.Values["code"];
}
else
{
this.lbl1.Text = "customer => " + this.RouteData.Values["code"]
+",doc_no=" + this.RouteData.Values["doc_no"];
}
foreach (var item in this.RouteData.Values.Keys)
{
System.Diagnostics.Debug.WriteLine("param=" + item
+ ",value=" + this.RouteData.Values[item]);
}
}
}
If you need to set the navigate URL in the hyperlink control, this can be done easily as shown below:
protected void Page_Load(object sender, EventArgs e)
{
// manually setup the URL based on the parameters.
this.all_cust_link.NavigateUrl = RouteTable.Routes.GetVirtualPath(null, "allcust", null).VirtualPath;
RouteValueDictionary d;
d = new RouteValueDictionary();
d.Add("code", "a001");
this.one_cust_link.NavigateUrl = RouteTable.Routes.GetVirtualPath(null, "cust", d).VirtualPath;
d = new RouteValueDictionary();
d.Add("code", "a001");
d.Add("doc_no", "inv123456");
this.one_doc_link.NavigateUrl = RouteTable.Routes.GetVirtualPath(null, "cust_doc", d).VirtualPath;
}
Then, in the global.asax file, add the following codes:
void Application_Start(object sender, EventArgs e)
{
// Code that runs on application startup
RegisterRoute(System.Web.Routing.RouteTable.Routes);
}
void RegisterRoute(System.Web.Routing.RouteCollection r)
{
// shows all customer
r.MapPageRoute("allcust",
"customer",
"~/customer.aspx");
// shows 1 customer profile
r.MapPageRoute("cust",
"customer/{code}",
"~/customer.aspx");
// shows the doc for the customer
r.MapPageRoute("cust_doc",
"customer/{code}/{doc_no}",
"~/customer.aspx");
// this allows any sub-level of parameters and the page requires to parse 'Page.RouteData.Values["queryvalues"]' (only 1 value).
// The caller can call this route by the following url:
// /cust
// /cust/a001
// /cust/a001/inv123456
//
r.MapPageRoute("cust_query",
"cust/{*queryvalues}",
"~/customer.aspx");
}
Finally, add all the aspx page that you need to display the information. In our example, we have only one page that is customer.aspx. You can get the parameter values in "Page.RouteData". Note: the last route that has been setup in the above was not shown in the following codes.
protected void Page_Load(object sender, EventArgs e)
{
if (this.RouteData.Values.Count == 0)
{
this.lbl1.Text = "cust list";
}
else
{
if (this.RouteData.Values.Count == 1)
{
this.lbl1.Text = "customer => " + this.RouteData.Values["code"];
}
else
{
this.lbl1.Text = "customer => " + this.RouteData.Values["code"]
+",doc_no=" + this.RouteData.Values["doc_no"];
}
foreach (var item in this.RouteData.Values.Keys)
{
System.Diagnostics.Debug.WriteLine("param=" + item
+ ",value=" + this.RouteData.Values[item]);
}
}
}
If you need to set the navigate URL in the hyperlink control, this can be done easily as shown below:
protected void Page_Load(object sender, EventArgs e)
{
// manually setup the URL based on the parameters.
this.all_cust_link.NavigateUrl = RouteTable.Routes.GetVirtualPath(null, "allcust", null).VirtualPath;
RouteValueDictionary d;
d = new RouteValueDictionary();
d.Add("code", "a001");
this.one_cust_link.NavigateUrl = RouteTable.Routes.GetVirtualPath(null, "cust", d).VirtualPath;
d = new RouteValueDictionary();
d.Add("code", "a001");
d.Add("doc_no", "inv123456");
this.one_doc_link.NavigateUrl = RouteTable.Routes.GetVirtualPath(null, "cust_doc", d).VirtualPath;
}
Thursday, April 23, 2015
ASP.NET UpdatePanel is not working in Google Chrome
I received a complaint from one of my user who said that the data grid not refreshing properly in Google Chrome.
Guess what..? I turned on the developer console in Chrome and found out that there is an error in loading some java script. After some research, I found the script below that fixed the problem:
Sys.Browser.WebKit = {};
if (navigator.userAgent.indexOf('WebKit/') > -1) {
Sys.Browser.agent = Sys.Browser.WebKit;
Sys.Browser.version = parseFloat(navigator.userAgent.match(/WebKit\/(\d+(\.\d+)?)/)[1]);
Sys.Browser.name = 'WebKit';
}
Guess what..? I turned on the developer console in Chrome and found out that there is an error in loading some java script. After some research, I found the script below that fixed the problem:
Sys.Browser.WebKit = {};
if (navigator.userAgent.indexOf('WebKit/') > -1) {
Sys.Browser.agent = Sys.Browser.WebKit;
Sys.Browser.version = parseFloat(navigator.userAgent.match(/WebKit\/(\d+(\.\d+)?)/)[1]);
Sys.Browser.name = 'WebKit';
}
Friday, October 3, 2014
Queue processing design
Queue concept is straight forward - append new item at the tail and dequeue from the head. It does not tell you how to process the requests in the queue.
You can enqueue new requests with multiple threads and processing the requests with 1 worker thread or multiple threads. Then, here comes with the processing designs:
Sequential (synchronous) processing the requests
With only 1 worker thread processing the requests in the queue, this is going to be slower as compared to the multiple worker threads but it has advantages. The advantage of sequential processing is when it comes to file I/O operation or network I/O operation. Just imagine that the exit has one door and only one person is able to pass through at one time.
Parallel (asynchronous) processing the requests
Thanks to multi-core and multi-CPU. With more than 1 worker thread processing the requests, it reduces the client wait time (on the response) and increases the server throughput.
Multi-thread has nothing to do with the "reducing the processing time for a request"! The "processing time" is depending on your codes and the how much the codes can be optimized (in the development time and also run time).
If there is a long run request and the request is handling by a worker thread (in a thread-pool), then, that worker thread is blocked. It won't be able to handle other request until it finished the current request. And if you have many of long running requests, then, all threads in the thread pool might not be able to reduce the response time. The server is now consider as "busy" even though the client is able to keep sending their requests into the queue.
Can we have unlimited threads in the pool..?!
The answer is no. If you have instantiated too many threads without using thread pool, then, the memory will be depleted... and the program will crash.
What happen to the requests in the queue if my program crash?
All requests will be disappear from the memory. Your program may be designed with auto-restart in case it crashed. But, you won't be able to recover the requests which was stored in the memory.
In case you need something better than storing the requests in memory..
1. Each thread in the thread pool must maintain individual processing statistics and these statistics will be updating to the database hourly. With this information, the system administrator will be able to tell how many worker threads were blocked, in which time zone and which request blocks it. Then, he will be able to justify whether to upgrade or replace the server.
The statistics to be keep track by each thread in the thread pool should include:
- Number of requests that has been processed
- Total processed time (ms)
- Highest processed time (ms)
- Request name for highest processed time
- Peak hour - 24 time zone in a day (just get the current hour value will do)
2. Using database as queue storage instead of memory - the main advantage is that the server will be able continue from where it's left before crashing. With this approach, there will be some overhead in storing the requests into database. But, those overhead is justifiable with the crash-proof design.
Another advantage of storing the requests into database is that the secondary server will be kick-in if all the worker threads in the thread pool (in the primary server) are blocked. The secondary server will be notify by the primary server (through WCF or socket). Then, the secondary server will query the database for the pending requests and process it accordingly.
Two ways to handle the respond to the client
- The result will be stored in the database and then notify the primary server. Then, the primary server will query the result and responds to the client.
- OR the result will be send to the primary server (through WCF or socket) and then responds to the client.
You can enqueue new requests with multiple threads and processing the requests with 1 worker thread or multiple threads. Then, here comes with the processing designs:
Sequential (synchronous) processing the requests
With only 1 worker thread processing the requests in the queue, this is going to be slower as compared to the multiple worker threads but it has advantages. The advantage of sequential processing is when it comes to file I/O operation or network I/O operation. Just imagine that the exit has one door and only one person is able to pass through at one time.
Parallel (asynchronous) processing the requests
Thanks to multi-core and multi-CPU. With more than 1 worker thread processing the requests, it reduces the client wait time (on the response) and increases the server throughput.
Multi-thread has nothing to do with the "reducing the processing time for a request"! The "processing time" is depending on your codes and the how much the codes can be optimized (in the development time and also run time).
If there is a long run request and the request is handling by a worker thread (in a thread-pool), then, that worker thread is blocked. It won't be able to handle other request until it finished the current request. And if you have many of long running requests, then, all threads in the thread pool might not be able to reduce the response time. The server is now consider as "busy" even though the client is able to keep sending their requests into the queue.
Can we have unlimited threads in the pool..?!
The answer is no. If you have instantiated too many threads without using thread pool, then, the memory will be depleted... and the program will crash.
What happen to the requests in the queue if my program crash?
All requests will be disappear from the memory. Your program may be designed with auto-restart in case it crashed. But, you won't be able to recover the requests which was stored in the memory.
In case you need something better than storing the requests in memory..
1. Each thread in the thread pool must maintain individual processing statistics and these statistics will be updating to the database hourly. With this information, the system administrator will be able to tell how many worker threads were blocked, in which time zone and which request blocks it. Then, he will be able to justify whether to upgrade or replace the server.
The statistics to be keep track by each thread in the thread pool should include:
- Number of requests that has been processed
- Total processed time (ms)
- Highest processed time (ms)
- Request name for highest processed time
- Peak hour - 24 time zone in a day (just get the current hour value will do)
2. Using database as queue storage instead of memory - the main advantage is that the server will be able continue from where it's left before crashing. With this approach, there will be some overhead in storing the requests into database. But, those overhead is justifiable with the crash-proof design.
Another advantage of storing the requests into database is that the secondary server will be kick-in if all the worker threads in the thread pool (in the primary server) are blocked. The secondary server will be notify by the primary server (through WCF or socket). Then, the secondary server will query the database for the pending requests and process it accordingly.
Two ways to handle the respond to the client
- The result will be stored in the database and then notify the primary server. Then, the primary server will query the result and responds to the client.
- OR the result will be send to the primary server (through WCF or socket) and then responds to the client.
Labels:
.Net,
C#,
Class,
Database,
Enhancement,
Exception,
Multi-threading,
Networking,
Optimization,
System Design,
System Development,
TCP,
WCF,
Web Service
Subscribe to:
Posts (Atom)