tag:blogger.com,1999:blog-61701254587177845122024-03-13T22:53:38.238-04:00Ranting and Dreaming ...Technology, Photography, Family, Food, and ConvictionsJohannes Setiabudihttp://www.blogger.com/profile/14347781769200608947noreply@blogger.comBlogger144125tag:blogger.com,1999:blog-6170125458717784512.post-21822433511567293202021-03-23T13:24:00.001-04:002021-03-23T13:30:29.452-04:00Lenovo Thinkpad TB3 Dock SagaAround fall 2020, my old desktop PC was on its last leg (it was running Intel Core Quad 9300 - so it was super old). I decided to get a reasonable powerful laptop with a dock that can drive two 2K (2560x1440) and one 1920x1200 monitors. So I purchased a X1 Carbon Extreme Gen 2 (running i7-9750H, 32 GB RAM) and Thinkpad Thunderbolt 3 Workstation Dock Gen 2 (40AN) - and I also purchased the Premier Support. But soon, it was apparent to me something was wrong.<br /><br />
<span class="fullpost">
I always use my laptop with the lid close, connected to the dock with the provided custom cable (which provide TB3 connection and power), with everything else connected to the dock (3 monitors, USBs, network).<br /><br />
The problem starts with intermittent screen flickering - which I initially dismissed as just small annoyance or maybe lose connection/cable. But it got to the point where it was annoying enough for me and I contacted Lenovo Premier Support. I started my case on January 4th and after about 11 weeks - it is now closed (but not resolved).<br />
<p style="margin-left: 25px;"><strong>Lenovo and their team has been very professional in responding to the issue. Every call I made to the Premier Support team or email exchanges have been helful and courteous. They deserve a lot of credit in investigating the issue and trying to find a good resolution with me.</strong></p>
So what is the main issue?
<p style="margin-left: 25px;"><strong>In short, to the user (me), the external monitors will blink (goes blank and comes back within a second or less) and some times will go blank for a period of time (ranging from two seconds to several minutes, or in some minor instances actually never come back). After about 8 weeks, Lenovo admitted that there is a bug in their dock - or to be precise in the chip (from Synaptics) they are using in the dock. The bug is related to HDCP - which is digital copy protection to prevent copying of digital audio and video content (i.e. during streaming). Since this chip is being used in all their TB3 docks (and Lenovo said also being used in other brands like DELL and HP docks) and it cannot be fixed via firmware update, so Lenovo said they do not have a workable solution for this issue (for me and other dock owners). I ended up trying to get a refund for my dock. </strong></p>
The issue was happening often enough for me to the point I was able to create reproducible steps (and the issue will happen 90% or more when followed):
<ul>
<li>Make sure laptop is connected to dock using the Lenovo provided TB3 cable</li>
<li>Make sure laptop lid is closed</li>
<li>Make sure external monitor(s) are connected through the dock</li>
<li>Login to Windows</li>
<li>Open Edge (or Chrome) in on of the external monitor</li>
<li>Go to https://www.netflix.com using that browser and login</li>
<li>Wait for the main Netflix page and one of the trailer on the banner to play in the background (usually screen starts to flicker or gone completely black at this point)</li>
<li>Click on one of the movies (any movies) and play it - then click the full-screen icon on the bottom right (sometime the flicker/black screen will happen here)</li>
<li>To repeat - you will need to close all browser windows and redo from going to Netflix website step</li>
</ul>
It also happened with https://eshop.macsales.com:
<ul>
<li>Laptop lid is closed, laptop is connected to dock via TB3</li>
<li>Restart laptop, login</li>
<li>Go to https://eshop.macsales.com/shop/docks (using any browser – but I was using Edge Chromium)</li>
<li>Scroll up and down on the website using the mouse wheel fast</li>
<li>Click on a link on the top menu (or go to a different page on the site) and scroll up and down</li>
<li>You will see one of the screen blink or blank (from quick blink to sometimes blank for several seconds)</li>
</ul>
If you are interested in the chronological timeline of the saga, here it is:
<ul>
<li>
Jan 4 - Call Premier Support, talked about the issue and technician diagnosed the issue maybe with the defective dock. Lenovo sent replacement dock (overnight).</li>
<li>Jan 6 - After trying the new dock, issue still persist, call Premier Support again. Technician did some digging and remoted into my machine and eventually concluded that maybe my main laptop board is defective (not the dock - since it is newly replaced) and scheduled a technician to come to my house to do a main board replacement on the laptop</li>
<li>Jan 8 - Technician showed up at my house and replace my X1 Extreme main board. Issue still persist and technician told me that he could not fix it since it is most likely not a laptop main board issue and recommended me to call Premier Support again</li>
<li>Jan 11 - Call Premier Support again. This time I recorded some videos of the issue happening. Technician was stumped and tried to escalate to his technical lead, but the lead has left and said he will follow up.</li>
<li>Jan 12 - Since I did not get any follow up, I called Premier Support again, but this time I was able to talk to a technical lead. The technical lead remoted into my machine and did some data collection and diagnostic. I also sent the videos of the issue. The lead said he would need to talk to an engineer about the issue. </li>
<li>Jan 19 - The technical lead emailed and sent me some information from the engineer and seeking more information about the dock (S/N, etc) and I sent the picture of the bottom of the dock which has all the information about the dock.</li>
<li>Jan 21 - A Lenovo System Support Engineer (Thinkpad Level 2) reached out to me confirming that he was able to replicate the issue. He asked me about my Windows update/version and whether I tried it with YouTube or other streaming service. I replied saying that I could get the issue to happen with DisneyPlus (I also sent video of it), but not with YouTube or CBS Plus. <div class="separator" style="clear: both;"><a href="https://1.bp.blogspot.com/-AQygfFNqIKM/YFoOX_edg6I/AAAAAAABR_0/uJOmJVOGm-s4Jb6DXWYF7JgANBvXp2dvwCPcBGAYYCw/s695/1-12-01.png" style="display: block; padding: 1em 0; text-align: center; "><img alt="" border="0" width="400" data-original-height="430" data-original-width="695" src="https://1.bp.blogspot.com/-AQygfFNqIKM/YFoOX_edg6I/AAAAAAABR_0/uJOmJVOGm-s4Jb6DXWYF7JgANBvXp2dvwCPcBGAYYCw/s400/1-12-01.png"/></a></div>
</li>
<li>Jan 25 - The System Support Engineer wanted me to test with FireFox - which I did and the issue did not happen. But the issue happened when using Netflix app (instead of the browser).</li>
<li>Jan 27 - The System Support Engineer escalated the issue to Product Engineering team</li>
<li>Feb 1 - The Product Engineering team escalated the issue to the dock engineer</li>
<li>Feb 9 - The dock engineer was able to replicate the issue in the lab and initial analysis seems to point to the HDCP as the cause of the issues
<div class="separator" style="clear: both;"><a href="https://1.bp.blogspot.com/-g_jO9KI1OR0/YFoaP4j4kYI/AAAAAAABR_4/Ptrvixj0FAkC2lRPTekm1iyuru7NqJt8wCLcBGAsYHQ/s695/2-9-01.png" style="display: block; padding: 1em 0; text-align: center; "><img alt="" border="0" width="400" data-original-height="314" data-original-width="695" src="https://1.bp.blogspot.com/-g_jO9KI1OR0/YFoaP4j4kYI/AAAAAAABR_4/Ptrvixj0FAkC2lRPTekm1iyuru7NqJt8wCLcBGAsYHQ/s400/2-9-01.png"/></a></div>
</li>
<li>Feb 16 - Lenovo says they were working with Synaptics (the chip's vendor) to try to come up with a solution
<div class="separator" style="clear: both;"><a href="https://1.bp.blogspot.com/-WL7o8HP41Ps/YFobXN8u3MI/AAAAAAABSAA/LhfwSArJY6kYhBDBNt-JTnsUN0wqCVX4gCLcBGAsYHQ/s695/2-16-01.png" style="display: block; padding: 1em 0; text-align: center; "><img alt="" border="0" width="400" data-original-height="314" data-original-width="695" src="https://1.bp.blogspot.com/-WL7o8HP41Ps/YFobXN8u3MI/AAAAAAABSAA/LhfwSArJY6kYhBDBNt-JTnsUN0wqCVX4gCLcBGAsYHQ/s400/2-16-01.png"/></a></div>
</li>
<li>Mar 1 - Lenovo minimized the issue and practically denied that this is an issue out of the ordinary - which I guess was probably because the engineer that replied was not the System Support Engineer that I have been working with, so he probably has not seen any video recordings I have done etc - so I responded to him by letting him know that his assessment was not my exprience and I sent him the videos I recorded of the issue.
<div class="separator" style="clear: both;"><a href="https://1.bp.blogspot.com/-GJlxYoRvtnE/YFoc5RGkfbI/AAAAAAABSAI/8I3OMWGWQ3IKn5pKD31EAsEFhE_iuivdgCLcBGAsYHQ/s695/3-1-01.png" style="display: block; padding: 1em 0; text-align: center; "><img alt="" border="0" width="400" data-original-height="469" data-original-width="695" src="https://1.bp.blogspot.com/-GJlxYoRvtnE/YFoc5RGkfbI/AAAAAAABSAI/8I3OMWGWQ3IKn5pKD31EAsEFhE_iuivdgCLcBGAsYHQ/s400/3-1-01.png"/></a></div>
</li>
<li>Mar 9 - The System Support Engineer updated me saying that Lenovo and Synaptics were able to replicate the issue and confirmed that issue was cause by HDCP calls.
<div class="separator" style="clear: both;"><a href="https://1.bp.blogspot.com/-wFo7PFiIB7w/YFoeP6NSANI/AAAAAAABSAQ/MrJmxbwSluI1uToDcHJSt8q1QRjtxoqvwCLcBGAsYHQ/s695/3-9-01.png" style="display: block; padding: 1em 0; text-align: center; "><img alt="" border="0" width="400" data-original-height="452" data-original-width="695" src="https://1.bp.blogspot.com/-wFo7PFiIB7w/YFoeP6NSANI/AAAAAAABSAQ/MrJmxbwSluI1uToDcHJSt8q1QRjtxoqvwCLcBGAsYHQ/s400/3-9-01.png"/></a></div></li>
<li>Mar 12 - After some emails back and forth (basically I was asking for clarification to help me understand the issue better), then Lenovo confirmed that the issue cannot be fixed in the current dock with the chipset.
<div class="separator" style="clear: both;"><a href="https://1.bp.blogspot.com/-vEkC7QBolFA/YFofPC5qzjI/AAAAAAABSAY/Xki7KAmZQOA2SnX3csRlguGNY8EhPEoUwCLcBGAsYHQ/s695/3-12-01.png" style="display: block; padding: 1em 0; text-align: center; "><img alt="" border="0" width="400" data-original-height="337" data-original-width="695" src="https://1.bp.blogspot.com/-vEkC7QBolFA/YFofPC5qzjI/AAAAAAABSAY/Xki7KAmZQOA2SnX3csRlguGNY8EhPEoUwCLcBGAsYHQ/s400/3-12-01.png"/></a></div></li>
<li>Mar 12 - I then responded by asking about the next step - should I get a refund? Or a replacement with the new 2021 dock? etc. <div class="separator" style="clear: both;"><a href="https://1.bp.blogspot.com/-QfPjrY1GlV8/YFogD990doI/AAAAAAABSAg/nIl8EthHXggQ8pGcJ5pzdS9kMs-ROBM6wCLcBGAsYHQ/s695/3-12-02.png" style="display: block; padding: 1em 0; text-align: center; "><img alt="" border="0" width="400" data-original-height="337" data-original-width="695" src="https://1.bp.blogspot.com/-QfPjrY1GlV8/YFogD990doI/AAAAAAABSAg/nIl8EthHXggQ8pGcJ5pzdS9kMs-ROBM6wCLcBGAsYHQ/s400/3-12-02.png"/></a></div></li>
<li>Mar 12 - After I sent my email, a different person from Lenovo replied (this person has been CC'd in most of the emails) minimizing the issue again, saying that it should only blink one second and no more - as well as saying that this is an "industrial wide limitation" and exhibited in other brands (HP, DELL). I assume that this person has probably not viewed my videos and I replied asking him that and also stating that my experience was certainly not as he described (blink 1 seconds or less and no more).
<div class="separator" style="clear: both;"><a href="https://1.bp.blogspot.com/-jw1gwbC0Klo/YFohchoQllI/AAAAAAABSAo/PZgaEh_eOosPfXGERimOULLVTGWQZzhsACLcBGAsYHQ/s695/3-12-03.png" style="display: block; padding: 1em 0; text-align: center; "><img alt="" border="0" width="400" data-original-height="303" data-original-width="695" src="https://1.bp.blogspot.com/-jw1gwbC0Klo/YFohchoQllI/AAAAAAABSAo/PZgaEh_eOosPfXGERimOULLVTGWQZzhsACLcBGAsYHQ/s400/3-12-03.png"/></a></div></li>
<li>Between Mar 12 - Mar 16, this person from Lenovo corresponded with me and basically doing some troubleshooting and logging. He would sent me a logger to execute and then I would run it and repeated my replication steps to reproduce the issue. Then I would send him logs. He would then send a debugging patch to execute and repeat the logging stuff, etc - repeated several times.</li>
<li>Mar 17 - The person I have been corresponding with then concluded the same conclusion - that the issue is related to HDCP authentication and cannot be fixed for the current dock with the current chip.
<div class="separator" style="clear: both;"><a href="https://1.bp.blogspot.com/-iTmbSCJB_7k/YFojGPicJBI/AAAAAAABSAw/B3QwUrdMJfg6WLp3X8IRXSJB2BlKzoDhQCLcBGAsYHQ/s695/3-17-01.png" style="display: block; padding: 1em 0; text-align: center; "><img alt="" border="0" width="400" data-original-height="259" data-original-width="695" src="https://1.bp.blogspot.com/-iTmbSCJB_7k/YFojGPicJBI/AAAAAAABSAw/B3QwUrdMJfg6WLp3X8IRXSJB2BlKzoDhQCLcBGAsYHQ/s400/3-17-01.png"/></a></div>
</li>
<li>Mar 17 - On that same day, Lenovo opened up a Customer Satisfaction team case to help me so I can get a refund on my dock.</li>
<li>Mar 22 - A member of Customer Satisfaction team reached out to me to help me with the process of getting a refund for my dock.</li>
</ul>
For the video recordings of the issue - they are available <a href="https://photos.app.goo.gl/nLTxh3WdYiY2Ubnd6" target="_blank">here</a> and <a href="https://photos.app.goo.gl/N2Q4ghfnoJwzbJ588" target="_blank">here</a>.
</span><div class="blogger-post-footer"><script type="text/javascript"><!--
google_ad_client = "pub-0606803324275047";
/* ContentAds */
google_ad_slot = "3381325551";
google_ad_width = 468;
google_ad_height = 60;
//-->
</script>
<script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js">
</script></div>Johannes Setiabudihttp://www.blogger.com/profile/14347781769200608947noreply@blogger.com2tag:blogger.com,1999:blog-6170125458717784512.post-61552239578220287812018-06-20T00:00:00.000-04:002018-06-22T10:39:08.064-04:00Switched to Project Fi and Save Some Money!For the longest time until October 2017, I was a faithful T-Mobile subscriber for my mobile phone. They offered the perfect combination of package that I need: 5 GB of high-speed data (it was 3G, 4G, and then LTE over-time), unlimited SMS, and some non-unlimited voice (100 minutes) - all for $30 per month. I was on that plan for years (along with my wife's phone). So we are paying around $64 per month for our cellphone bills combined. We both rarely talk over the phone, most of our communication is happening via data (SMS, WhatsApp, Slack, Skype, etc), and we also have a home phone (VOIP) that we use for long-calling (like calling family members, customer supports, etc etc that are minutes intensive). <br/><br/>
I have heard about Project Fi when it came out - somehow it was not that attractive for me at the time. Until I have to buy a new phone ... <br/><br/>
<div class="fullpost">
<div class="separator" style="clear: both; text-align: center;"><a href="https://1.bp.blogspot.com/-V0dqWm8XUvw/Wy0JqHEzA7I/AAAAAAABEaU/qvUn7Bt-r3M9UT2KHTPKQt-2whKg7Qr9wCLcBGAs/s1600/ProjectFi.jpg" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" src="https://1.bp.blogspot.com/-V0dqWm8XUvw/Wy0JqHEzA7I/AAAAAAABEaU/qvUn7Bt-r3M9UT2KHTPKQt-2whKg7Qr9wCLcBGAs/s400/ProjectFi.jpg" width="400" height="168" data-original-width="800" data-original-height="336" /></a></div><b>So what is Project Fi?</b> It is basically Google's phone carrier - but instead of using their own towers etc, Google uses three existing providers (T-Mobile, Sprint, US Cellular) to provide the subscribers their mobile data services. Project Fi approved devices will be able to switch among all the providers seamlessly. Regular GSM devices will be able to use T-Mobile service only. Project Fi is also a PREPAID service - so there is no contract, no cancellation fee, etc. Which is awesome!<br/><br/>
<b>How does it save me money?</b> I thought paying ~$30 per month is cheap already!? Project Fi simplifies your billing - so basically the main account holder must pay $20 per month for the main line - which includes unlimited talk and text. Now on top of it, you only pay for the data that you use with rate $10 per GB. So if I am only using .5GB this month, I will only billed for ~$25 before tax. I don't know about you - but in my day-to-day, 80-90% of the time I am always connected to WiFi (home, office, my friend's house, free WiFi at a coffee shop/restaurant, etc) - and I have my stats too from the last year of my T-Mobile which shows that on average that I only use around 0.7GB of LTE data per month. With that, I am potentially saving $5-$7 per month - not much, I know - but I gain so much more (better coverage, unlimited call, better support, and simplified billing). <br/><br/>
<b>Then ... </b>Google released this app call Datally. You can read more about it <a href="https://play.google.com/store/apps/details?id=com.google.android.apps.freighter&hl=en_US">here</a>. Which basically allows you to control data usage on an app-by-app. The default setting is to not allow data if the app is not on the foreground. So by installing this app, my LTE data usage actually goes down from ~0.7GB to ~0.5GB per month. <br/><br/>
<b>Better with more people!</b> It gets better! I switched my wife's provider from T-Mobile to Project Fi - now because she is not the main account holder (her account is working under my account), she is only billed $15 per month (for unlimited calls and text) instead of $20 (like me because I am the main/primary account). Then our data usage is combined and billed together. So what this means is that if we are assuming that her data usage is similar like me (between 0.5-0.7GB of LTE data per month), at the end of the month our combined cell phone bill is ~$49 ($20 + $15 + $7 + $7) before tax - which is ~$12-$14 saving per month. Not much, but we'll take it. <br/><br/>
<b>Bill Protection Benefit!</b> Project Fi has other advantages - such as: "Bill Protection" - which is a cap of your bill (not your data usage), but a cap of actually how much Google can actually bill you. The cap is $60 for your LTE data. So if you are on a single account plan, that would $80 total ($20 phone & text + $60 data). Or a different way to look at it is this is the "Unlimited Plan" for Project Fi subscriber. <br/><br/>
<b>Free International SMS & Data</b>! With Project Fi, you can use SMS and data freely (already included) in around 170 countries. You can also make or receive phone calls, but it is not free. <br/><br/>
You can read more about Project Fi <a href="https://fi.google.com">here</a>.
</div><div class="blogger-post-footer"><script type="text/javascript"><!--
google_ad_client = "pub-0606803324275047";
/* ContentAds */
google_ad_slot = "3381325551";
google_ad_width = 468;
google_ad_height = 60;
//-->
</script>
<script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js">
</script></div>Johannes Setiabudihttp://www.blogger.com/profile/14347781769200608947noreply@blogger.com0tag:blogger.com,1999:blog-6170125458717784512.post-85641429916501875072018-06-17T21:00:00.000-04:002018-06-22T09:54:53.138-04:00Drawing Multiple Routes In One Map with Google Map<div class="separator" style="clear: both; text-align: center;"><a href="https://4.bp.blogspot.com/-8BKuyQ2qBE8/Wyz-gH4k8VI/AAAAAAABEaM/Ms5rS6ZhVRMJ4VjsF2zEOfCO6Ie5k3CjQCPcBGAYYCw/s1600/g_map.PNG" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" src="https://4.bp.blogspot.com/-8BKuyQ2qBE8/Wyz-gH4k8VI/AAAAAAABEaM/Ms5rS6ZhVRMJ4VjsF2zEOfCO6Ie5k3CjQCPcBGAYYCw/s640/g_map.PNG" width="640" height="435" data-original-width="437" data-original-height="297" /></a></div>Lots of examples in Google Map tutorials and examples are about drawing directions, markers, searches - and all of them typically involve only drawing one set of markers into a "route". An example of this will be going from Boston, MA to Seattle, WA via Cleveland, OH then Chicago, IL. But I need to be make Google Map to draw multiple routes in a single map. <br/><br/>
The purpose of this task is to compare the routes. Assuming, like the example above, I am going from Boston to Seattle - which route will be better? Or maybe closer to passing Detroit? <br/><br/>
Or another use case: if my friend and I are both traveling out of NYC on 2 separate cars, I am going to Columbus, OH and my friend is going to Detroit, MI - and another friend needs to be dropped off in State College, PA - so which car should this friend join - my car or the other car?<br/><br/>
<div class="fullpost">
So here is how you do it. <br/>
FIRST - you will need to get Google Map Javascript API Key - you can get it <a href="https://developers.google.com/maps/documentation/javascript/get-api-key">here</a>. Once you get it and enable the needed APIs, you will need to use it in your script reference below. <br/><br/>
SECOND - in your HTML markup - add script reference Google Map and substituting the YOUR_API_KEY with your real API key from above.
<pre class="prettyprint linenums">
<!DOCTYPE html>
<html>
<head>
<style type="text/css">
#map {
margin-top: 10px;
height: 750px;
width: 100%;
}
</style>
</head>
<body>
<div id="map"></div>
<script async defer src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&callback=initMap" type="text/javascript"></script>
</body>
</html>
</pre>
THIRD - initialize several global variables and create a javascript method called "initMap" - this is to initialize your map once the script from Google is loaded.
<pre class="prettyprint linenums">
var routeMapArray = [];
var map;
var defaultZoom = 11;
var directionsService;
var directionDisplayRenderEngineArray = [];
var iconColor = '';
function initMap() {
routeMapArray = [];
directionsService = new google.maps.DirectionsService();
// center initially on NYC
loadMap(new google.maps.LatLng(40.7128, -74.0060));
}
</pre>
FOURTH - Create a method called "loadMap" that takes 1 parameter and create a json payload for your routes.
<pre class="prettyprint linenums">
var loadMap = function(center) {
var mapOptions = {
zoom: 11,
mapTypeControl: false,
streetViewControl: false,
mapTypeId: google.maps.MapTypeId.ROADMAP
};
if (center !== undefined && center !== null) {
mapOptions.center = center;
}
// Some basic map setup (from the API docs)
map = new google.maps.Map(document.getElementById('map'), mapOptions);
// Start the request making
var requestArray = [{
"origin": "New York, NY",
"destination": "Seattle, WA",
"waypoints": [{
"location": "Detroit, MI",
"stopover": true
},
{
"location": "Denver, CO",
"stopover": true
}
],
"travelMode": "DRIVING"
},
{
"origin": "Boston, MA",
"destination": "San Diego, CA",
"waypoints": [{
"location": "Dayton, OH",
"stopover": true
},
{
"location": "Las Vegas, NV",
"stopover": true
}
],
"travelMode": "DRIVING"
}
];
processRequests(requestArray);
};
</pre>
FIFTH - We need to process the requests in order, one by one - using different renderer each time on the same map (this is the key).
<pre class="prettyprint linenums">
var processRequests = function(requestArray) {
var bounds = new google.maps.LatLngBounds();
var infoWindow = new google.maps.InfoWindow();
// Counter to track request submission and process one at a time;
var i = 0;
var position = "";
// Used to submit the request 'i'
var submitRequest = function() {
if (requestArray.length === 0) return;
directionsService.route(requestArray[i], processDirectionResults);
};
// Used as callback for the above request for current 'i'
function processDirectionResults(result, status) {
if (status === google.maps.DirectionsStatus.OK) {
// Create a unique DirectionsRenderer 'i'
directionDisplayRenderEngineArray[i] = new google.maps.DirectionsRenderer();
directionDisplayRenderEngineArray[i].setMap(map);
if (i == 0) {
bounds = new google.maps.LatLngBounds();
}
iconColor = "#000000";
directionDisplayRenderEngineArray[i].setOptions({
preserveViewport: true,
polylineOptions: {
strokeWeight: 5,
strokeOpacity: 0.8,
strokeColor: iconColor
},
markerOptions: {}
});
// Use this new renderer with the result
directionDisplayRenderEngineArray[i].setDirections(result);
// adjust zooming
if (i === 0) {
bounds = directionDisplayRenderEngineArray[i].getDirections().routes[0].bounds;
} else {
bounds.union(directionDisplayRenderEngineArray[i].getDirections().routes[0].bounds);
}
map.fitBounds(bounds);
if (map.zoom > defaultZoom) {
map.setZoom(defaultZoom);
}
// and start the next request
setTimeout(function() {
nextRequest();
}, 500);
}
}
function nextRequest() {
// increment counter
i++;
// next request if any
if (i >= requestArray.length) {
// No more to do
return;
}
// submit next request
submitRequest();
}
// begin processing
submitRequest();
};
</pre>
SIXTH - Done. Make. profit. Here is a working <a href="https://jsfiddle.net/xvbw2hsa/39/">JSFiddle</a> for it.
</div><div class="blogger-post-footer"><script type="text/javascript"><!--
google_ad_client = "pub-0606803324275047";
/* ContentAds */
google_ad_slot = "3381325551";
google_ad_width = 468;
google_ad_height = 60;
//-->
</script>
<script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js">
</script></div>Johannes Setiabudihttp://www.blogger.com/profile/14347781769200608947noreply@blogger.com0tag:blogger.com,1999:blog-6170125458717784512.post-73493829450769002072015-03-23T20:30:00.000-04:002015-03-24T10:24:38.139-04:00Best Dishes in ColumbusI have lived in Columbus more than half of my life and throughout the years I have a growing food & dishes that I think are the best in the city. So without further ado, here they are. If you have any suggestions, please leave them in the comment.
<div class="fullpost">
The list will be in CATEGORY - RESTAURANT - NAME OF DISH
<br /><br />
<b>East-Asian Food</b>
<ul>
<li>Tofu Dish - Happy Dragon (Livingston Ave) - Pipa Tofu</li>
<li>Hand-made Noodle - Jiu Thai (Bethel Rd) - Beef Stretch Noodle</li>
<li>Pho (Vietnamese Noodle Soup) - Buckeye Pho (Bethel Rd) - Pho Dac Biet</li>
<li>Phad Thai - Bangkok (Refugee Rd) - Phad Thai</li>
<li>Salt & Pepper Squid - Panda Inn (Bethel Rd) - Salt & Pepper Calamari</li>
<li>Spicy Diced Chicken - Coco's Grill (5th Ave) - Hot Spicy Chicken (extra crispy)</li>
<li>Dumplings - Sesame Sea (Muirfield Dr) - Firecracker Wontons</li>
<li>Pork Chop - Sun Flower (Sawmill Rd) - Crispy Ribs</li>
<li>General Tso Dish - General Tso's Restaurant (Godown Rd) - General Tso's Beef</li>
<li>Sushi Dish - Akai Hana (Old Henderson Rd) - Any Sushi</li>
<li>Chicken Wings - Yau's Bistro (N High St) - Spicy Wings</li>
<li>Spicy Pork - Happy Dragon (Livingston Ave) - Jalapeno Pork</li>
<li>Pork Belly Dish - Little Dragon (Morse Rd) - Mecai Pork </li>
<li>Crispy Whole Chicken - Pacific Eatery (Kenny Rd) - Crispy Chicken</li>
</ul>
<br />
<b>Latin Food</b>
<ul>
<li>Pastor (pork) Taco - Los Guachos Taqueria (Godown Rd) - Pastor Taco</li>
<li>Fish Taco (fried) - El Arepazo (Pearl St) - Fish Taco</li>
<li>Empanada - El Arepazo (Pearl St) - Pork/Beef Empanada</li>
<li>Latin Sandwiches - Si Senor (Lynn St) - ChicharrĂ³n Peruano</li>
<li>Huevos Rancheros - Star Liner Diner (Cemetery Rd) - Huevos Rancheros</li>
<li>Chiliquiles - Star Liner Diner (Cemetery Rd) - Chiliquiles</li>
<li>Cheese Empanada - El Manantial Latino (Hudson St) - Cheese & Pineapple Empanada</li>
<li>Pork Belly Taco - Nada (Nationwide Blvd) - Crispy Pork Belly Taco</li>
</ul>
<br />
<b>Western Food</b>
<ul>
<li>Classic Calamari - Eddie Merlot (Polaris) - Sesame Calamari</li>
<li>Pork Belly - The Market Italian Village (Summit St) - Braised Pork Belly</li>
<li>Chicken Wings - Rooster's - Chipotle Garlic Wings</li>
<li>Gyro - Yanny's (Cleveland Ave) - Small Lamb Gyro</li>
<li>Pizza - Benny's Pizza (Marysville) - BLT Pizza</li>
</ul>
</div><div class="blogger-post-footer"><script type="text/javascript"><!--
google_ad_client = "pub-0606803324275047";
/* ContentAds */
google_ad_slot = "3381325551";
google_ad_width = 468;
google_ad_height = 60;
//-->
</script>
<script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js">
</script></div>Johannes Setiabudihttp://www.blogger.com/profile/14347781769200608947noreply@blogger.com0tag:blogger.com,1999:blog-6170125458717784512.post-35817209758924962592015-03-22T15:30:00.000-04:002015-03-24T10:45:56.280-04:00Installing DNN Platform in Azure WebsiteI was installing DNN Platform on an Azure Website following this <a href="http://www.dnnsoftware.com/community-blog/cid/154968/creating-a-dnn-installation-on-windows-azure-websites">documentation </a>on DNN website. But, for whatever reason, my installation never finished and stopped at 18% and gave up. The log button shows nothing. Trying the "Retry" button produces SQL error for basically trying to create existing DB elements.
<div class="fullpost">
So I proceeded to delete my website and its DB - and try again from scratch. But after 2-3 times trying and getting the same result over and over again, I started to dig into the internet and found this <a href="http://www.dnnsoftware.com/forums/threadid/516007/scope/posts/dnn-from-the-azure-website-gallery">post </a>- which basically saying:<br /><br />
After installing the website and prior to installing the DNN platform, change the scale from "FREE" to "STANDARD" on your website. Stop & restart - and then proceed to install DNN.
</div ><div class="blogger-post-footer"><script type="text/javascript"><!--
google_ad_client = "pub-0606803324275047";
/* ContentAds */
google_ad_slot = "3381325551";
google_ad_width = 468;
google_ad_height = 60;
//-->
</script>
<script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js">
</script></div>Johannes Setiabudihttp://www.blogger.com/profile/14347781769200608947noreply@blogger.com0tag:blogger.com,1999:blog-6170125458717784512.post-64751424666266441302015-03-10T18:25:00.000-04:002015-03-19T09:26:56.264-04:00Tire Tips For Your CarIn maintaining a car, tire purchase is always expensive but necessary. Every 4-5 years on average, you need to replace your tires. Depending on the size/measurement and type of your tires, this could translate to $300 to $1,000 to just purchasing new tires alone. Then adding cost of balancing, mounting, getting rid of your old tires, etc - you add another about $40 per tire. Although you only do this every 4-5 years, it is still quite costly. So how can we do some cost saving in this tire related stuff?<br /><br/>
<div class="fullpost">
<b><i>TIRE LIFETIME</i></b><br />
Using your tire as long as possible is one way to do it, but if you are using bald tire, it can be very dangerous. So how can we get the best lifetime of our tires? There are three simple principles: tire pressure, balancing, and alignment. If you keep these 3 things in line then you have the possibility to get the most out of your tire lifetime. These 3 things basically deal with the evenness of your tire thread wear. If you have under-inflated tire(s), or an unbalanced tire(s), or misaligned car, it will create uneven wear - which will reduce the lifetime of your tire. <br />
<i>TIPS:</i>
<ul><li> The standard manufacturer recommended tire pressure usually listed on a sticker inside one of your door.</li>
<li>Most tire shop/place (Firestone, Goodyear, DiscountTire, etc) will check pressure and inflate your tire for free of charge. </li>
<li>Most tire shop (at least I know Firestone, NTB, and DiscountTire) have a lifetime balancing deal. So you pay a fee ($15 per tire), but then you can balance your tires anytime you want as long as you own the same set of tires. I balance my tires every 6,000 miles or every 6 months. This is a lot cheaper compared to what the car dealership is charging you. </li>
<li>Most tire shop (at least I know Firestone) have a lifetime alignment deal. So you pay a fee ($170 at Firestone), but then you can your car anytime you want as long as you own the car (some places only goes for 3-4 years). I check my alignment every time I balance my tires - every 6,000 miles or every 6 months. For a single standard alignment, it usually costs you about $70 - so within 3 alignments, I already got my money's worth.</li>
</ul>
<br />
<b><i>TIRE PURCHASE</i></b><br />
You can get your tire measurements from your own tire, just look at the side wall and it will be written there, usually in this format: XXX/YY/RZZ. My minivan has these measurements: 235/65/R16. Or, you can go to <a href="http://www.tirerack.com">www.tirerack.com</a> and enter your car's year, make, and model - then it will tell you the original tire sizes/measurements.
<br /><br />
Once you have your measurements/sizes, go to <a href="http://www.tirerack.com">www.tirerack.com</a>, click "Tires" and then "Survey Results". Now depending on your car type, and tire type, you want to select the tire type that you want. A few tips: "Summer" tire is unusable in the winter time, "Winter" tire is usable in non-winter times but has short lifetime. Since I live in Ohio, I always buy an "All Season" tire - which means I don't have to replace the set of tire seasonally. So I open each "All Season" option in a new tab in my browser and enter in my tire size on the left side under "FILTER BY" of the window and close the tabs that yielded no results. From that point on, it is just a matter of taste, preference, price, and survey results. For example, for my minivan (235/65/R16), I am choosing General AltiMAX RT43 (T-Speed Rated) as my top pick. Now if you are buying new tires, you put the tire in your basket, and pay and checkout from TireRack.com - you can ship it to your home, in which then you will need to take them to the tire shop to be installed later on, or you can ship them directly to the tire shop that you will be installing (assuming you already made an appointment with them).
<br /><i>TIPS:</i>
<ul>
<li>If you are buying new tires from the big-box store, always go to <a href="http://www.discounttire.com">Discount Tire</a>. They always have the best price. Always. They even price-match.</li>
<li>Always ask for lifetime balancing during your installation - you pay a bit more, but worth it. Read "TIRE LIFETIME" above. </li>
<li>Never go to Monroe, Walmart, Sears, Midas, Meineke</li>
<li>If you prefer local store instead of the chain-shops, go to <a href="http://www.katztires.com/">Katz Tires</a> in Columbus. Good people, fast, awesome prices, excellent customer satisfaction</li>
</ul>
<br />
<b><i>PURCHASING USED TIRES</i></b><br />
Another option is to buy used tires. There is a huge market for used tires - from junk yards, people swapping rims & tires, etc. These tires usually still have about 50-90% thread left, which means 2-4 years (or more). Now why used tires? Because they are "CHEAP" - I am talking REALLY REALLY CHEAP. Here is an example, a new tire for Continental ProContact (245/45/R18) cost about $210 per tire. In the used market, with 80% thread, you can get one for about $50-$60 (including mounting, balancing, and disposing your old tire). If you are not picky about brand, you can get cheaper ones that only cost about $35-$45 per tire for that size. For smaller size (for like a Civic or an Elantra etc), it can be cheaper still. I have seen someone walk out the door with 4 tires installed, balanced, and dispose old tires for $100. Getting used tire(s) is ideal when you do not need a whole set - sometimes you just need 1 tire or maybe a pair because of flat, sidewall damage, etc. Used tires are not ideal if you are a stickler to a particular brand/type, or if you have rare sizes - since they are used and cannot be special ordered.
<br /><i>TIPS:</i>
<ul>
<li>Know what you want in terms of sizes and brand/type. I would use the survey from <a href="http://www.tirerack.com">TireRack</a> to narrow down my list of brands/types. </li>
<li>Call lots of places and compare prices, thread-wear, and availability. In Columbus alone there are about 20+ used tire shops.</li>
<li>Read shops review. Some shops are really friendly, nice, and professional. Some are just plain rude and questionable.</li>
</ul>
<br />
My tire store is <a href="http://www.katztires.com/">Katz Tires</a>. They are old-school, been in Columbus forever, well-known tire shop in Columbus. They sell new and used tires. Their service is always impeccable, fast, economical, satisfaction guaranteed. They are a tire shop - so they sell, mount, and fix tires - nothing else. Katz is always the first shop I call if I need a used tire or a plug/patch. Here is a story where they just treat you above and beyond: I came in the other day because of a leak in one of my tire. My tire pressure indicator alerted me about it and I need to put about 10 psi of air every week. So after 2 weeks of leakage, I brought it to Katz. They took care of me right away, dismounted the wheel, checked for leaks, inspected the tire, and after 10 minutes or so, one of their staff came to me and said that they could not find a leak. I told them my story and they thought it was a just small leakage from dirty rim lip. So they cleaned the rim and the tire, remounted the tire into the rim, re-balanced the wheel, mounted the wheel into the car. When the staff gave me my key back and I was ready to pay - he said "free of charge, no need to pay". So I was a bit confused and asked him "Really? Are you sure?" He replied, "Absolutely. 100%. Bring it back if it still leaking and we will take another look." He just won a customer for life.
<br />Don't believe me about Katz? Check out their <a href="http://www.yelp.com/biz/katz-tires-columbus">Yelp review</a>.
<br /><br />
There is also a Firestone nearby my house where I do all my lifetime balancing and alignments. They treat me right, rarely push any services beyond what I need, and they are close to there I live. I also like Discount Tire, they are generally great, but they do not do alignment. So balancing & alignment in separate places just create too much hassle for me.
</div ><div class="blogger-post-footer"><script type="text/javascript"><!--
google_ad_client = "pub-0606803324275047";
/* ContentAds */
google_ad_slot = "3381325551";
google_ad_width = 468;
google_ad_height = 60;
//-->
</script>
<script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js">
</script></div>Johannes Setiabudihttp://www.blogger.com/profile/14347781769200608947noreply@blogger.com0tag:blogger.com,1999:blog-6170125458717784512.post-27765250994291472552015-02-21T11:16:00.000-05:002015-03-11T11:18:46.495-04:00Using GMail as email service from Azure Following these posts:
<ul><li><a href="http://www.asp.net/mvc/overview/security/create-an-aspnet-mvc-5-web-app-with-email-confirmation-and-password-reset">Create a secure ASP.NET MVC 5 web app with log in, email confirmation and password reset</a></li>
<li><a href="http://azure.microsoft.com/en-us/documentation/articles/sendgrid-dotnet-how-to-send-email/">How to Send Email Using SendGrid with Azure</a></li>
</ul>
I have successfully setup my Azure hosted ASP.NET MVC project to send email via SendGrid. But, SendGrid is a bit slow, which is irritating when the email is for registration confirmation - which I want to be as fast as possible, so if a user registers, that user will get an email within a minute or faster to confirm his/her email to complete registration. If the use must wait longer than that, chances are that the registration will never be continued and finished. So I wanted to use a faster email service - why not GMail?
<br /><br />
<div class="fullpost">
Here is the SendGrid code:
<pre class="prettyprint linenums">
private static async Task SendGridSendAsync(string to, string from, string subject, string body)
{
var myMessage = new SendGridMessage();
myMessage.AddTo(to);
myMessage.From = new System.Net.Mail.MailAddress(
"my.service.email.address@gmail.com", "my.service");
myMessage.Subject = subject;
myMessage.Text = body;
myMessage.Html = body;
var credentials = new NetworkCredential(
"sendGridAccount", "sendGridPassword"
);
// Create a Web transport for sending email.
var transportWeb = new SendGrid.Web(credentials);
// Send the email.
if (transportWeb != null)
{
await transportWeb.DeliverAsync(myMessage);
}
else
{
Trace.TraceError("Failed to create Web transport.");
await Task.FromResult(0);
}
}
</pre>
Here is the GMail code:
<pre class="prettyprint linenums">
private static async Task GmailSendAsync(string to, string from, string subject, string body)
{
var sentFrom = (string.IsNullOrEmpty(from) ? "my.service.email.address@gmail.com" : "my.service");
// Configure the client:
System.Net.Mail.SmtpClient client = new System.Net.Mail.SmtpClient("smtp.gmail.com");
client.Port = 587;
client.DeliveryMethod = System.Net.Mail.SmtpDeliveryMethod.Network;
client.UseDefaultCredentials = false;
// Creatte the credentials:
System.Net.NetworkCredential credentials = new NetworkCredential(
"gmailAccount", "gmailPassword"
);
client.EnableSsl = true;
client.Credentials = credentials;
// Create the message:
var mail = new System.Net.Mail.MailMessage(sentFrom, to);
mail.Subject = subject;
mail.Body = body;
await client.SendMailAsync(mail);
}
</pre>
</div><div class="blogger-post-footer"><script type="text/javascript"><!--
google_ad_client = "pub-0606803324275047";
/* ContentAds */
google_ad_slot = "3381325551";
google_ad_width = 468;
google_ad_height = 60;
//-->
</script>
<script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js">
</script></div>Johannes Setiabudihttp://www.blogger.com/profile/14347781769200608947noreply@blogger.com0tag:blogger.com,1999:blog-6170125458717784512.post-72980662153679153732015-01-30T23:00:00.000-05:002015-01-31T13:56:42.214-05:00Explicit operatorAccording to MSDN, explicit operator keyword declares a user-defined type conversion operator that must be invoked with a cast. This is super handy if you are doing a lot of conversion/translation in your code.
<br /><br />
Here is an converting between two separate classes, Person and Contact - which have translatable properties between them.
<pre class="prettyprint linenums">
public class Contact {
public string Name { get; set; }
public string PrimaryPhone { get; set; }
public string PrimaryEmail { get; set; }
}
public class Person{
public int PersonId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string HomePhone { get; set; }
public string HomeEmail { get; set; }
public string WorkPhone { get; set; }
public string WorkEmail { get; set; }
public string CellPhone { get; set; }
}
</pre>
<div class="fullpost">
Then, you can create an explicit conversion operator such as this:
<pre class="prettyprint linenums">
public static explicit operator Contact (Person person)
{
return new Contact
{
Name = person.FirstName + " " + person.LastName,
PrimaryPhone = (String.IsNullOrEmpty(person.CellPhone) ?
(String.IsNullOrEmpty(person.WorkPhone) ?
person.HomePhone : person.WorkPhone) : person.CellPhone),
PrimaryEmail = (String.IsNullOrEmpty(person.WorkEmail) ? person.HomeEmail : person.WorkEmail)
};
}
</pre>
Here is how you would use it:
<pre class="prettyprint linenums">
Contact contact = (Contact)person;
</pre>
</div><div class="blogger-post-footer"><script type="text/javascript"><!--
google_ad_client = "pub-0606803324275047";
/* ContentAds */
google_ad_slot = "3381325551";
google_ad_width = 468;
google_ad_height = 60;
//-->
</script>
<script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js">
</script></div>Johannes Setiabudihttp://www.blogger.com/profile/14347781769200608947noreply@blogger.com0tag:blogger.com,1999:blog-6170125458717784512.post-83883255576965402942015-01-20T19:30:00.000-05:002015-01-31T13:40:46.896-05:00Enabling IIS Express to Accept External RequestI am doing a web development project where the web application needs to look good on all browsers (on Mac and PC - Win7 and Win8) and mobile devices (iPad, Windows Phone, Android, etc). So most of my testing can be done in my development machine - either by running an emulator from Visual Studio, or letting the browser (i.e. Safari) emulate iPad/iPhone resolution. This probably solved 95% of the issues and problems. But, then I still want to do a real device test - where I actually run the web application and browse to it from an iPad, or a Mac, from an Android phone, etc.
<br />
<br />
Of course - the ideal solution is just to put the web application somewhere on the internet (i.e. http://mytestwebsite.mydomain.com) and let the testing begin. But unfortunately, I do not have the luxury of a QA or test site. So I just have to run it locally.
<br />
<br />
I can set it up on my IIS, but that seems to be a hassle - removing/stopping my current site running in IIS, setting up this new site, creating all the permissions, app pool, etc. Too much overhead for simple testing. Can I just hit F5, or run the IIS Express and make it accepting external request - so I can hit the site from outside of my dev machine? YES!
<div class="fullpost">
<h4>Allowing everyone to access http://mydevmachine:80</h4>
Open up an admin elevated command prompt and type in this command:
<pre>
netsh http add urlacl url=http://mydevmachine:80/ user=everyone
</pre>
<br />
<h4>Open up firewall</h4>
Now, we need to open up the Windows Firewall for allow traffic to go through. In the same command prompt from above, type in this command:
<pre>
netsh firewall add portopening TCP 80 IISExpressWeb enable ALL
</pre>
<br />
<h4>Configure IIS Express</h4>
Lastly, we need to add our machine name into the IIS Express configuration. You can find the config file located in <strong>C:\Users\<YOURNAME>\Documents\IISExpress\config</strong>. Open the file using any text editor and find the web application configuration. My web application is named "Domain.Site.Mvc" in this example.
<pre class="prettyprint linenums">
<site name="Domain.Site.Mvc" id="4">
<application path="/" applicationPool="Clr4IntegratedAppPool">
<virtualDirectory path="/" physicalPath="c:\Projects\Domain.Site\Domain.Site.Mvc" />
</application>
<bindings>
<binding protocol="http" bindingInformation="*:12555:localhost" />
<binding protocol="http" bindingInformation="*:80:mydevmachine" />
</bindings>
</site>
</pre>
<br />
Now, I can start my site on my dev machine and then browse to it from separate machines, Mac machine, phone, tablets, etc by just going to http://mydevmachine/. With the exception of an iPad. For whatever reason, iPad does not allow browsing to a local machine name - it has to be a fully qualified domain name. I will write a solution to this on a separate blog post.
</div>
<div class="blogger-post-footer"><script type="text/javascript"><!--
google_ad_client = "pub-0606803324275047";
/* ContentAds */
google_ad_slot = "3381325551";
google_ad_width = 468;
google_ad_height = 60;
//-->
</script>
<script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js">
</script></div>Johannes Setiabudihttp://www.blogger.com/profile/14347781769200608947noreply@blogger.com0tag:blogger.com,1999:blog-6170125458717784512.post-89719569686961193032013-10-29T20:42:00.000-04:002013-11-12T20:47:10.101-05:00Custom Textbox Editor TemplateASP.NET MVC's built-in editor template is awesome - by just decorating your model, the Html helper EditorFor knows how to read those attributes and display appropriate html elements and html attributes, including validation, display name, or even using custom templates that we specify through DataType or UIHint attributes.
<br />
<br />
Now, all my string properties in my models have string length attribute, to prevent truncation when they being saved in the database, or for formatting reason. Now the built-in Html helper EditorFor is not smart enough to translate that StringLength attribute to the actual html attribute of maxlength. Secondly, it is also not smart enough to adjust the width of the textbox based on that StringLength - so a ZipCode property which only need 5 characters should have smaller textbox compared to a CompanyName property which needs 50 characters. <br /><div class="fullpost">
So I made an editor template that do just that. You can copy the code below, put it in a partial view called "string.cshtml" placed within "EditorTemplates" folder under "/Views/Shared/". I use Twitter Bootstrap css to adjust my textbox width (using "input-mini", "input-large" classes) - feel free to substitute them with whatever you are using.
<pre class="prettyprint linenums">
@model String
@using System.Globalization
@using System.Linq
@{
IEnumerable<ModelValidator> validators = ModelValidatorProviders.Providers.GetValidators(ViewData.ModelMetadata, ViewContext);
ModelClientValidationRule stringLengthRule = validators
.SelectMany(v => v.GetClientValidationRules())
.FirstOrDefault(m => m.ValidationType == "length");
if (stringLengthRule != null && stringLengthRule.ValidationParameters.ContainsKey("max"))
{
int maxLength = int.Parse(stringLengthRule.ValidationParameters["max"].ToString());
string inputSize = "input-xxlarge";
if (maxLength < 5)
{
inputSize = "input-mini";
}
else if (maxLength < 10)
{
inputSize = "input-small";
}
else if (maxLength < 30)
{
inputSize = "input-medium";
}
else if (maxLength < 75)
{
inputSize = "input-large";
}
else if (maxLength < 150)
{
inputSize = "input-xlarge";
}
Dictionary<string, object> attributes = new Dictionary<string, object> {
{ "class", inputSize },
{ "maxlength", maxLength.ToString(CultureInfo.InvariantCulture) }
};
@Html.TextBox("", Model, attributes)
}
else
{
Dictionary<string, object> attributes = new Dictionary<string, object> {
{ "class", "input-medium" }
};
@Html.TextBox("", Model, attributes)
}
}
</pre>
</div><div class="blogger-post-footer"><script type="text/javascript"><!--
google_ad_client = "pub-0606803324275047";
/* ContentAds */
google_ad_slot = "3381325551";
google_ad_width = 468;
google_ad_height = 60;
//-->
</script>
<script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js">
</script></div>Johannes Setiabudihttp://www.blogger.com/profile/14347781769200608947noreply@blogger.com0tag:blogger.com,1999:blog-6170125458717784512.post-78665187510708609282013-09-03T20:42:00.000-04:002013-11-11T11:43:38.134-05:00How to expose internal methods to a different assemblyLet's say you create this method - let's call it MethodX. Now MethodX is only used within the project - so you marked it as "internal". So now you want to test this MethodX.
<br /><br />
Now, I know that in theory MethodX is used internally in the project, which exposing public members of public classes etc - and those public classes and their members are the ones ought to be tested. Using MethodX or not is an implementation detail that the consumer does not need to know, hence does not need to be tested specifically. Yupe, agreed - that is all good.
<br /><div class="fullpost">
Now, let's set that aside for a moment - and if you want to test the internal MethodX directly, how would you do it? Of course, the easiest solution is by making MethodX (and the class containing it) to be public. But, then you must remember to convert it back (and forth) every time you run your tests - which is quickly becoming a pain.
<br /><br />
It turns out, there is a flag/attribute that you can set in your class declaration to specifically mark a class and its internals to be visible to a different set of assembly. Take a look at this example below:
<pre class="prettyprint linenums">
[assembly:InternalsVisibleTo("MyProject.Tests")]
namespace MyProject
{
public class MyClass
{
internal void MethodX()
{
// ...
}
}
}
</pre>
So normally, although the class MyClass is public and is available to public, but the method MethodX is not accessible from outside the same assembly. But, upon putting the InternalsVisibleToAttribute, now MethodX is accessible from MyProject.Tests assembly. If this consuming assembly is your test project, then you can start to build a test for it just like testing against a public method.
</div><div class="blogger-post-footer"><script type="text/javascript"><!--
google_ad_client = "pub-0606803324275047";
/* ContentAds */
google_ad_slot = "3381325551";
google_ad_width = 468;
google_ad_height = 60;
//-->
</script>
<script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js">
</script></div>Johannes Setiabudihttp://www.blogger.com/profile/14347781769200608947noreply@blogger.com0tag:blogger.com,1999:blog-6170125458717784512.post-61427622871412217942013-08-24T10:30:00.000-04:002013-11-09T23:34:25.075-05:00How to create a unit test that expect an exception?In creating unit tests, we want to get maximum code coverage to make sure we are testing all possible scenarios and outcomes of our code. When we have a possible path of execution that leads into an exception being thrown, how do we build a test for that?
<br /><div class="fullpost">
Consider this simple method "GoToPage" that takes in an integer as a parameter in the "Book" class.
<pre class="prettyprint linenums">
public class Book
{
// ...
public void GoToPage(int page)
{
if (page < 1)
throw new ArgumentException("Page must be a positive, non-zero integer", "page");
if (page > TotalPage)
throw new ArgumentException("Page cannot be more than the total page count", "page");
// ...
}
// ...
}
</pre>
So how do we test those boundary scenarios? We can do something like this:
<pre class="prettyprint linenums">
[TestMethod]
public void NegativePage_Exception()
{
// arrange
var book = new Book();
// act
try
{
// act
book.GoToPage(-1);
}
catch (ArgumentException e)
{
// assert
Assert.AreEqual("Page must be a positive, non-zero integer", e.Message);
}
catch (Exception) {
Assert.Fail();
}
}
</pre>
Or, you can write a more concise test such as this:
<pre class="prettyprint linenums">
[TestMethod]
[ExpectedException(typeof(ArgumentException), "Page must be a positive, non-zero integer")]
public void NegativePage_Exception()
{
// arrange
var book = new Book();
// act
book.GoToPage(-1);
}
</pre>
</div><div class="blogger-post-footer"><script type="text/javascript"><!--
google_ad_client = "pub-0606803324275047";
/* ContentAds */
google_ad_slot = "3381325551";
google_ad_width = 468;
google_ad_height = 60;
//-->
</script>
<script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js">
</script></div>Johannes Setiabudihttp://www.blogger.com/profile/14347781769200608947noreply@blogger.com0tag:blogger.com,1999:blog-6170125458717784512.post-64935737970677042092013-08-22T23:04:00.000-04:002013-11-09T23:06:05.966-05:00Creating data validation unit testCreating a unit test is pretty simple for a method, but when your business/POCO objects are relying on DataAnnotation for validation, how do you make sure that they are implemented and tested? Obviously, one can create all the classes run the test from the UI and check whether one can enter invalid data. Is there another way of doing this instead of waiting to test them from the UI? If we are doing TDD, can we build the test first?
<br /><div class="fullpost">
For example, let's say we have this class "Person", which requires that both first name and last name to be required, but middle name is optional.
<pre class="prettyprint linenums">
public class Person
{
[Required]
public string FirstName { get; set; }
public string MiddleName { get; set; }
[Required]
public string LastName { get; set; }
}
</pre>
There are several ways to create tests for this class - the first one is to check whether the property is decorated with the RequiredAttribute.
<pre class="prettyprint linenums">
[TestMethod]
public void Person_FirstName_IsRequired()
{
// arrange
var propertyInfo = typeof(Person).GetProperty("FirstName");
// act
var attribute = propertyInfo.GetCustomAttributes(typeof(RequiredAttribute)).Cast<RequiredAttribute>().FirstOrDefault();
// assert
Assert.IsNotNull(attribute);
}
</pre>
Or another way is by testing the validation itself.
<pre class="prettyprint linenums">
[TestMethod]
public void Person_FirstName_IsRequired()
{
// arrange
var person = new Person();
var context = new ValidationContext(person, null, null);
var validationResults = new List<ValidationResult>();
// act
var isValid = Validator.TryValidateObject(person, context, validationResults);
// assert
Assert.IsTrue(validationResults.Any(e => e.ErrorMessage == "The FirstName field is required"));
}
</pre>
</div><div class="blogger-post-footer"><script type="text/javascript"><!--
google_ad_client = "pub-0606803324275047";
/* ContentAds */
google_ad_slot = "3381325551";
google_ad_width = 468;
google_ad_height = 60;
//-->
</script>
<script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js">
</script></div>Johannes Setiabudihttp://www.blogger.com/profile/14347781769200608947noreply@blogger.com0tag:blogger.com,1999:blog-6170125458717784512.post-11794806742865430552013-04-10T21:19:00.000-04:002013-04-10T21:29:04.767-04:00Handling 404 Error in ASP.NET MVCIn ASP.NET Web Forms, handling 404 errors are easy - which is basically a web.config setting. In ASP.NET MVC, it is a bit more complicated. Why is it more complicated? In comparison, everything is seemingly easier in MVC than WebForm. <br />
<br />
It is more complicated mainly because of Routing. In WebForm, most 404 occurs because of non-existent file and each UR: is usually mapped to a particular file (aspx). With MVC, that is not the case. All requests are handled by the Routing table and based on that it will invoke appropriate controller and actions etc. Secondly, our basic default route usually is quite common ({controller}/{action}/{id}) - therefore most URL request will be caught by this route. <br />
<br />
So, let's dive in on how can we do proper handling of 404 errors with ASP.NET MVC.<br />
<div class="fullpost">
<h3>
TURN ON CUSTOM ERROR IN WEB.CONFIG</h3>
<pre class="prettyprint linenums">
<customErrors mode="On" defaultRedirect="~/Error/Error">
<error statusCode="404" redirect="~/Error/Http404" />
</customErrors>
</pre>
<br />
<h3>
DECLARE DETAIL ROUTES MAPPED IN ROUTE TABLE</h3>
So instead of just using the default route:
<pre class="prettyprint linenums">
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
</pre>
Declare all your intended route explicitly and create a "catch-all" to handle non-matching route - which basically a 404. In MVC, a 404 can happen when you try to access a URL where the there is no controller for. This code in the routing table handles that scenario.
<pre class="prettyprint linenums">
routes.MapRoute(
name: "Account",
url: "Account/{action}/{id}",
defaults: new { controller = "Account", action = "Index", id = UrlParameter.Optional }
);
routes.MapRoute(
name: "Home",
url: "Home/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
routes.MapRoute(
name: "Admin",
url: "Admin/{action}/{id}",
defaults: new { controller = "Admin", action = "Index", id = UrlParameter.Optional }
);
routes.MapRoute(
name: "404-PageNotFound",
url: "{*url}",
defaults: new { controller = "Home", action = "Http404" }
);
</pre>
<h3>
OVERRIDE HANDLEUNKNOWNACTION IN BASECONTROLLER CLASS</h3>
Create a Controller base class that every controller in your application inherits from. In that base controller class, override HandleUnknownAction method. Now, another scenario that a 404 may happen is that when the controller exists, but there is no action for it. In this case, the routing table will not be able to trap it easily - but the controller class has a method that handle that.
<pre class="prettyprint linenums">
[AllowAnonymous]
public class ErrorController : Controller
{
protected override void HandleUnknownAction(string actionName)
{
if (this.GetType() != typeof(ErrorController))
{
var errorRoute = new RouteData();
errorRoute.Values.Add("controller", "Error");
errorRoute.Values.Add("action", "Http404");
errorRoute.Values.Add("url", HttpContext.Request.Url.OriginalString);
View("Http404").ExecuteResult(this.ControllerContext);
}
}
public ActionResult Http404()
{
return View();
}
public ActionResult Error()
{
return View();
}
}
</pre>
<h3>
CREATE CORRESPONDING VIEWS</h3>
View for generic error: Error.chtml
<pre class="prettyprint linenums">
@model System.Web.Mvc.HandleErrorInfo
@{
ViewBag.Title = "Error";
}
<hgroup class="title">
<h1 class="error">Error.</h1>
<br />
<h2 class="error">An error occurred while processing your request.</h2>
@if (Request.IsLocal)
{
<p>
@Model.Exception.StackTrace
</p>
}
else
{
<h3>@Model.Exception.Message</h3>
}
</hgroup>
</pre>
View for 404 error: Http404.chtml
<pre class="prettyprint linenums">
@model System.Web.Mvc.HandleErrorInfo
@{
ViewBag.Title = "404 Error: Page Not Found";
}
<hgroup class="title">
<h1 class="error">We Couldn't Find Your Page! (404 Error)</h1><br />
<h2 class="error">Unfortunately, the page you've requested cannot be displayed. </h2><br />
<h2>It appears that you've lost your way either through an outdated link <br />or a typo on the page you were trying to reach.</h2>
</hgroup></pre>
</div><div class="blogger-post-footer"><script type="text/javascript"><!--
google_ad_client = "pub-0606803324275047";
/* ContentAds */
google_ad_slot = "3381325551";
google_ad_width = 468;
google_ad_height = 60;
//-->
</script>
<script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js">
</script></div>Johannes Setiabudihttp://www.blogger.com/profile/14347781769200608947noreply@blogger.com1tag:blogger.com,1999:blog-6170125458717784512.post-82513575281718521002013-04-07T09:43:00.001-04:002013-04-07T09:43:20.377-04:00Properly Deleting Database for Database-MigrationI am working on a simple brand new project with ASP.NET MVC and decided to try EF to connect to my database. This gives me the opportunity to learn EF Code-First, database-migration, etc. <br />
<br />
Everything seems to be pretty intuitive until when I am running "update-database" from the Package Manager Console. I created my Configuration class, turn-on automatic migration, and populated my database using Seed method, made sure all my context are correct. <br />
<br /><div class="fullpost">
<h3>
[TL;DR]</h3>
When one need to delete/recreate a database, do not delete the mdf file from App_Data, but instead go to "SQL Server Object Explorer" and find your database under (localdb) and delete it from there. <br />
<br />
<h3>
FULL VERSION:</h3>
<a href="http://2.bp.blogspot.com/-3vk-fMGG2t4/UWFvL-2sieI/AAAAAAABCSA/EDt99bOoDMI/s1600/solution_explorer.PNG" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" height="320" src="http://2.bp.blogspot.com/-3vk-fMGG2t4/UWFvL-2sieI/AAAAAAABCSA/EDt99bOoDMI/s320/solution_explorer.PNG" width="245" /></a><br />
Then, since I want to recreate my database, I delete my database (mdf file) from my App_Data folder under Solution Explorer and then run "update-database". See picture on the left.<br />
<br />
But then I am getting an error:<br />
<pre class="prettyprint linenums">Cannot attach the file D:\Projects\MvcApplication1\App_Data\aspnet-MvcApplication1-20130407085115.mdf' as database 'MvcApplication1'.
</pre>
I looked in the file explorer and the mdf file is surely gone. Try to close Visual Studio and reopen, same error. <br />
<br />
Well, the database was initially created when I try to "Register" or create an account using the site (it's using SimpleMembershipProvider) - so maybe it will recreate it if I simply run the site and try to register again. But then I am getting the same error when running the website. <br />
<br />
I went to the recycle bin, restore the mdf file and ran the project again - it worked. It did not have the new tables or new data, but no "cannot attach" error. Restoring this file also restore my default connection. If I try to delete the mdf file again, then my project won't run and my database-migration also won't run. <br />
<br />
I almost resort to think that "Code-First" is a lie - that I simply have to add the new tables manually in the SQL table designer, etc. This is so confusing - should not be that hard, I think.<br />
<br />
<a href="http://3.bp.blogspot.com/-ZgjKvln0dxM/UWFyouQqQnI/AAAAAAABCSM/oqABmUdirJ4/s1600/server_explorer_empty.PNG" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" src="http://3.bp.blogspot.com/-ZgjKvln0dxM/UWFyouQqQnI/AAAAAAABCSM/oqABmUdirJ4/s1600/server_explorer_empty.PNG" /></a>So with the mdf file deleted, I went to Server Explorer and checked that my connection to the database is gone - there is nothing under "Data Connections". <br />
<br />
So maybe I need to delete the data connection instead of deleting the mdf file from App_Data? So I did a restore again from my Recycle Bin, made sure my project ran, and then I deleted my connection from the Server Explorer, rebuilt the project and ran it. It worked! But my delight is short-lived, since then I realized that deleting the connection does not necessarily mean deleting the database - which I quickly checked that my mdf file still in the App_Data folder. I simply deleted the connection to view the database via Visual Studio. <br />
<br />
I tried more and more things - which increase the frustration level - because at this point I am stuck in my project and all I have been trying to do is to modify my database schema. If I did this using the old way using SQL Management Studio or SQL Express, or even Linq-to-SQL, I could have been making a huge progress in my project. <br />
<br />
<h3>
SOLUTION</h3>
<div class="separator" style="clear: both; text-align: center;">
<a href="http://3.bp.blogspot.com/-COslal1Qz58/UWF2U7jJDqI/AAAAAAABCSY/tgqq_4rCBRw/s1600/sql_server_explorer.PNG" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" src="http://3.bp.blogspot.com/-COslal1Qz58/UWF2U7jJDqI/AAAAAAABCSY/tgqq_4rCBRw/s1600/sql_server_explorer.PNG" /></a></div>
Until I stumbled on "SQL Server Object Explorer" under "VIEW" in your VS Studio 2012 top menu/toolbar. I noticed that although my mdf file is deleted from my App_Data folder, but under "SQL Server Object Explorer", my database is still registered under (localdb)\v11.0\Databases.<br />
<br />
Out of curiosity, I deleted my database from there and re-ran my project - it worked. It recreated my database (without the new tables and seed data). So I deleted it again from SQL Object Explorer, and ran "update-database" - it ran successfully this time. EUREKA!!<br />
<br />
So lesson learned: when one need to delete/recreate a database, do not delete the mdf file from App_Data, but instead go to "SQL Server Object Explorer" and find your database under (localdb) and delete it from there.
<br /><br />
</div><div class="blogger-post-footer"><script type="text/javascript"><!--
google_ad_client = "pub-0606803324275047";
/* ContentAds */
google_ad_slot = "3381325551";
google_ad_width = 468;
google_ad_height = 60;
//-->
</script>
<script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js">
</script></div>Johannes Setiabudihttp://www.blogger.com/profile/14347781769200608947noreply@blogger.com0tag:blogger.com,1999:blog-6170125458717784512.post-76690241750500554682013-04-01T13:34:00.000-04:002013-04-01T13:34:00.665-04:00Crumbtrail ActionFilterRecently, I had to make a crumb-trail in the web application that I am working on (ASP.NET MVC). There are multiple ways of doing this and initially I elected to do this in my controller base class (which is inherited by all my controller classes). I created a method that do the job - but this means that this method has to be called on every single action (with GET method). If a fellow developer miss to call the method, then it would mean that the data in the crumb-trail is not built properly or accurately. If there is just an interceptor that I can hook into that will run automatically every time a controller action is being called ... *sigh
<br /><br />
Wait - there is one, ActionFilter!!
<br /><div class="fullpost">
So I created an action filter and via configuration register and apply it to all controllers. The logic in my code handles the exceptions to the apply all (such as Account controller, POST method, and non-authenticated users). Here is the code to the ActionFilter code:
<pre class="prettyprint linenums">
public class CrumbTrailKeyValuePair<TKey, TValue>
{
public CrumbTrailKeyValuePair() { }
public CrumbTrailKeyValuePair(TKey key, TValue value)
{
Key = key;
Value = value;
}
public TKey Key { get; set; }
public TValue Value { get; set; }
}
public class CrumbTrailAttribute : ActionFilterAttribute
{
public override void OnResultExecuting(ResultExecutingContext filterContext)
{
if (filterContext.HttpContext.User.Identity.IsAuthenticated)
{
// skip recording Account controller actions
if (filterContext.RouteData.Values["controller"] != null &&
filterContext.RouteData.Values["controller"].ToString() != "Account")
{
// put in cookies
Queue<CrumbTrailKeyValuePair<string, string>> crumbTrailQueue =
new Queue<CrumbTrailKeyValuePair<string, string>>();
HttpCookie crumbTrailCookie =
filterContext.HttpContext.Request.Cookies["CrumbTrailLinks"] ?? new HttpCookie("CrumbTrailLinks");
// initialize serializer
var serializer = new JavaScriptSerializer();
// if crumbTrailCookie is not empty, retrieve value from cookie the rehydrate queue
if (crumbTrailCookie != null && !string.IsNullOrEmpty(crumbTrailCookie.Value))
{
// rehydrate crumbTrailQueue with cookie value
crumbTrailQueue =
new Queue<CrumbTrailKeyValuePair<string, string>>
(serializer.Deserialize<IEnumerable<CrumbTrailKeyValuePair<string, string>>>
(HttpUtility.UrlDecode(crumbTrailCookie.Value)));
}
if (filterContext.HttpContext.Request.HttpMethod.ToUpper() == "GET")
{
// get page title
var pageTitle = string.IsNullOrEmpty(filterContext.Controller.ViewBag.Title) ?
"PAGE" : filterContext.Controller.ViewBag.Title;
// get url
string url = filterContext.HttpContext.Request.RawUrl;
// if current page is not in both queue, then add it
if (!crumbTrailQueue.Any(x => x.Value == url && x.Key == pageTitle))
{
// remove oldest menu item, keep queue length to 6
if (crumbTrailQueue.Count >= 5)
crumbTrailQueue.Dequeue();
// insert new menu item into queue
crumbTrailQueue.Enqueue(new CrumbTrailKeyValuePair<string, string>(pageTitle, url));
}
crumbTrailCookie.Value = serializer.Serialize(crumbTrailQueue);
crumbTrailCookie.Expires = DateTime.Now.AddDays(365);
crumbTrailCookie.Path = "/";
filterContext.HttpContext.Response.Cookies.Add(crumbTrailCookie);
}
// put in viewbag
if (filterContext.Result.GetType().Name == "ViewResult")
{
(filterContext.Result as ViewResult).ViewBag.QuickAccessQueue = crumbTrailQueue;
}
}
}
}
}
</pre>
Inside the view, just get the queue from the ViewBag and display accordingly. The queue is stored temporarily in a cookie, so it will be remembered even when the browser is closed and reopen and relogin.
</div><div class="blogger-post-footer"><script type="text/javascript"><!--
google_ad_client = "pub-0606803324275047";
/* ContentAds */
google_ad_slot = "3381325551";
google_ad_width = 468;
google_ad_height = 60;
//-->
</script>
<script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js">
</script></div>Johannes Setiabudihttp://www.blogger.com/profile/14347781769200608947noreply@blogger.com0tag:blogger.com,1999:blog-6170125458717784512.post-62390928285611253232013-03-25T21:55:00.000-04:002014-01-05T19:06:23.901-05:00Creating Validation Adapter for Validation AttributeIf your project is rather big, often you would like your validation to reside on the business layer instead of on the UI layer. So for example, using our <a href="http://setiabud.blogspot.com/2013/03/creating-custom-validation-attribute.html">previous code sample</a>, it would make perfect sense if the custom attribute code resides in your business layer project (instead of on the UI project). BUT, to enable client validation (<a href="http://setiabud.blogspot.com/2013/03/custom-validation-attribute-client-side.html">here for example</a>), implementing IClientValidatable is required and IClientValidatable is within the System.Web.Mvc namespace. This means that your business layer needs to reference System.Web.Mvc. But why would you want to do that - referencing a UI related reference in your business layer project?
<br/ ><br/ >
When we look at the built-in validator (such as RequiredAttribute), how did they do this? If we look at the documentation, it is within System.ComponentModel.DataAnnotations.ValidationAttribute namespace and deriving from System.Object - there is no dependency to System.Web.Mvc at all. We can use that validation attribute in WinForm, Silverlight, WebForm, WPF, etc.
<br/ >
<div class="fullpost"><br/ >
There is no magic - the ASP.NET guys simply built a validation adapter for it in the System.Web.Mvc namespace. In this post, I am going to show you how to build one for our MagicNumber validator.
<br/ ><br/ >
So let's clarify on our namespacing issue - in our business layer, let's call that MyApp.Library project (namespace: MyApp.Library), which includes MyClass.cs (namespace: MyApp.Library or MyApp.Library.MyClass to be complete) and our custom validation attribute MagicNumberAttribute.cs (namespace: MyApp.Library or MyApp.Library.MagicNumberAttribute to be complete).
<br/ ><br/ >
Here is the code for MyApp class:
<pre class="prettyprint linenums">
public class MyClass {
// ... more code
[MagicNumber(ErrorMessage = "Sum is not correct")]
public int MyData { get; set; }
// ... more code
}
</pre>
Here is our code for the custom validation attribute in MagicNumberAttribute.cs:
<pre class="prettyprint linenums">
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)]
sealed public class MagicNumberAttribute : ValidationAttribute {
// constructor to accept the comparison property name
public MagicNumberAttribute(string thirtySevenProperty) : base() {
if (thirtySevenProperty == null) {
throw new ArgumentNullException("thirtySevenProperty");
}
ThirtySevenProperty= thirtySevenProperty;
}
// property to store the comparison field name
public string ThirtySevenProperty{ get; private set; }
protected override ValidationResult IsValid(object value, ValidationContext validationContext) {
// get property info of the "ThirtySevenProperty"
PropertyInfo thirtySevenPropertyInfo = validationContext.ObjectType.GetProperty(ThirtySevenProperty);
// check if that property exists / valid
if (thirtySevenPropertyInfo == null) {
return new ValidationResult(String.Format(CultureInfo.CurrentCulture, "UNKNOWN PROPERTY", ThirtySevenProperty));
}
// get the value of the property
object thirtySevenPropertyValue = thirtySevenPropertyInfo.GetValue(validationContext.ObjectInstance, null);
// do comparison and return validation result with error if not equal
if (!Equals(value, thirtySevenPropertyValue)) {
return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
}
// return null if everything is ok
return null;
}
}
</pre>
We'll call our ASP.NET MVC project to be MyApp.UI.Mvc (namespace: MyApp.UI.Mvc).
Here is our javascript from the previous post, we'll reuse it, put it in a js file:
<?prettify lang=js linenums=true?>
<pre class="prettyprint">
jQuery.validator.unobtrusive.adapters.addSingleVal("magicnumber", "other");
jQuery.validator.addMethod("magicnumber", function (val, element, other) {
var modelPrefix = element.name.substr(0, element.name.lastIndexOf(".") + 1)
var otherVal = $("[name=" + modelPrefix + other + "]").val();
if (val && otherVal) {
return val == otherVal;
}
return true;
);
</pre>
So now, instead of implementing IClientValidatable, we will be creating a validation adapter. We will put this in a file in our MyApp.UI.Mvc project, let's name it "MagicNumberAttributeAdapter.cs". The code is simple:
<?prettify lang=cs linenums=true?>
<pre class="prettyprint">
public class MagicNumberAttributeAdapter : DataAnnotationsModelValidator<MagicNumberAttribute>
{
public MagicNumberAttributeAdapter(ModelMetadata metadata, ControllerContext context, MagicNumberAttribute attribute)
: base(metadata, context, attribute)
{
}
public override IEnumerable<ModelClientValidationRule> GetClientValidationRules()
{
var rule = new ModelClientValidationRule
{
ErrorMessage = this.ErrorMessage,
ValidationType = "magicnumber",
};
rule.ValidationParameters.Add("other", this.Attribute.ThirtySevenProperty);
yield return rule;
}
}
</pre>
To make the connection between the adapter and the validation attribute itself, we need to let MVC know about it - so we need to register it inside the global.asax.cs file:
<pre class="prettyprint">
DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(MagicNumberAttribute), typeof(MagicNumberAttributeAdapter));
</pre>
Once all those are setup, our custom validator will fire on the client-side (as well as server-side), we have clear separation of concern - where the business layer does not reference the UI as dependency, and our custom validation attribute is also reusable. Awesome!
<br /><br />
Additional reading:
<ul>
<li><a href="http://setiabud.blogspot.com/2013/03/creating-custom-validation-attribute.html">Creating Custom Validation Attribute</a>
</li>
<li><a href="http://setiabud.blogspot.com/2013/03/in-last-post-i-went-through-steps-to.html">
Creating Custom Validation Attribute With Dependency To Other Property</a>
</li>
<li><a href="http://setiabud.blogspot.com/2013/03/custom-validation-attribute-client-side.html">
Custom Validation Attribute - Client Side Enabled!</a>
</li>
<li><a href="http://setiabud.blogspot.com/2013/03/custom-validation-attribute-client-side_17.html">Custom Validation Attribute - Client Side Enabled With Custom Rule</a></li>
</ul>
</div><div class="blogger-post-footer"><script type="text/javascript"><!--
google_ad_client = "pub-0606803324275047";
/* ContentAds */
google_ad_slot = "3381325551";
google_ad_width = 468;
google_ad_height = 60;
//-->
</script>
<script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js">
</script></div>Johannes Setiabudihttp://www.blogger.com/profile/14347781769200608947noreply@blogger.com2tag:blogger.com,1999:blog-6170125458717784512.post-29097738580724174852013-03-17T21:04:00.000-04:002013-03-29T16:27:28.192-04:00Custom Validation Attribute - Client Side Enabled With Custom RuleIn the previous post, I went over on how to <a href="http://setiabud.blogspot.com/2013/03/custom-validation-attribute-client-side.html">enable client-side validation for our custom validation attribute - using a pre-build jQuery validator</a>. Now what if you want to create your own custom script - because your validator needs to do something truly custom that the pre-build validator is not covering? No problem!
<br />
<div class="fullpost">
Modify your code to this:
<?prettify lang=cs linenums=true?>
<pre class="prettyprint">
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)]
sealed public class MagicNumberAttribute : ValidationAttribute, IClientValidatable {
// constructor to accept the comparison property name
public MagicNumberAttribute(string thirtySevenProperty) : base() {
if (thirtySevenProperty == null) {
throw new ArgumentNullException("thirtySevenProperty");
}
ThirtySevenProperty= thirtySevenProperty;
}
// property to store the comparison field name
public string ThirtySevenProperty{ get; private set; }
protected override ValidationResult IsValid(object value, ValidationContext validationContext) {
// get property info of the "ThirtySevenProperty"
PropertyInfo thirtySevenPropertyInfo = validationContext.ObjectType.GetProperty(ThirtySevenProperty);
// check if that property exists / valid
if (thirtySevenPropertyInfo == null) {
return new ValidationResult(
String.Format(CultureInfo.CurrentCulture, "UNKNOWN PROPERTY", ThirtySevenProperty));
}
// get the value of the property
object thirtySevenPropertyValue = thirtySevenPropertyInfo.GetValue(validationContext.ObjectInstance, null);
// do comparison and return validation result with error if not equal
if (!Equals(value, thirtySevenPropertyValue)) {
return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
}
// return null if everything is ok
return null;
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(
ModelMetadata metadata, ControllerContext context) {
var rule = new ModelClientValidationRule
{
ErrorMessage = this.ErrorMessage,
ValidationType = "magicnumber",
};
rule.ValidationParameters.Add("other", OtherProperty);
yield return rule;
}
}
</pre>
As we see, in "GetClientValidationRules", I am returning a "ModelClientValidationRule" with "ValidationType" set to "magicnumber". This string is the javascript method name. The name itself is not important, but they need to be consistent between what you put here and the actual method name in the javascript method itself. I am also adding parameters into it - which then will be transformed into a name-value pair that can be retrieved in our javascript method.
<br />
<br />
Now create a javascript file to be included in your project (it must be referenced AFTER the basic jquery.js and jquery.validate.js and jquery.validaten.unobstrusive.js).
<?prettify lang=js linenums=true?>
<pre class="prettyprint">
jQuery.validator.unobtrusive.adapters.addSingleVal("magicnumber", "other");
jQuery.validator.addMethod("magicnumber", function (val, element, other) {
var modelPrefix = element.name.substr(0, element.name.lastIndexOf(".") + 1)
var otherVal = $("[name=" + modelPrefix + other + "]").val();
if (val && otherVal) {
return val == otherVal;
}
return true;
);
</pre>
The first line is to connect our javascript method "magicnumber" into the validation event and the rest is our client-side script to do client side validation. Our "magicnumber" method takes in "other" parameter, which will hold the value for the name for our comparison property/field - we need that to get the value of the field. We also want to consider if our model is using prefix - beyond that it is just doing a dom selector and get the value and returning a comparison result.
<br />
<br />
Additional reading:
<ul>
<li><a href="http://setiabud.blogspot.com/2013/03/creating-custom-validation-attribute.html">Creating Custom Validation Attribute</a>
</li>
<li><a href="http://setiabud.blogspot.com/2013/03/in-last-post-i-went-through-steps-to.html">
Creating Custom Validation Attribute With Dependency To Other Property</a>
</li>
<li><a href="http://setiabud.blogspot.com/2013/03/custom-validation-attribute-client-side.html">
Custom Validation Attribute - Client Side Enabled!</a>
</li>
<li><a href="http://setiabud.blogspot.com/2013/03/creating-validation-adapter-for.html">Creating Validation Adapter for Validation Attribute</a></li>
</ul>
</div>
<div class="blogger-post-footer"><script type="text/javascript"><!--
google_ad_client = "pub-0606803324275047";
/* ContentAds */
google_ad_slot = "3381325551";
google_ad_width = 468;
google_ad_height = 60;
//-->
</script>
<script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js">
</script></div>Johannes Setiabudihttp://www.blogger.com/profile/14347781769200608947noreply@blogger.com2tag:blogger.com,1999:blog-6170125458717784512.post-5073386686702069992013-03-12T00:10:00.000-04:002013-03-29T16:27:34.294-04:00Custom Validation Attribute - Client Side Enabled!In previous posts, I outlined <a href="http://setiabud.blogspot.com/2013/03/creating-custom-validation-attribute.html">how to make a custom validation attribute</a> - including <a href="http://setiabud.blogspot.com/2013/03/in-last-post-i-went-through-steps-to.html">a more complex one that depends on another property</a>. Both of those validators are working - but they are working on the server-side. So if you have an ASP.NET MVC web application, the validation will fire after it gets to the server side within your controller. Ideally, we want to do client-side validation as well - so that the form does not need to POST if the data is not valid. How do we do that?<br />
<br />
We can make custom javascript, query the fields manually and check for conditions - but this code will be disconnected from the custom validator that we created. Doing custom javascript also means that it will be so specific that the likelihood of being reusable is very very small. <br />
<br />
Also, as we have seen with the built-in validators that shipped with ASP.NET MVC, they seem to just work - no custom javascript needed per field or manual client-side validation. When a property or a field is decorated with the correct attribute and client-side validation is enabled in the web.config, then boom - it just works. How can we make our validator to behave in similar manner?<br />
<div class="fullpost">
<br />
One way to do this is by implementing IClientValidatable in your custom validator. Implementing this interface means we must implement a method called "GetClientValidationRules". This method is what basically will become a connector for our validator to the client-side. The property decorated with our custom validation attribute will then outputting flags that will indicate that it should be evaluated during client-side validation. In this method as well we need to provide additional information to properly evaluate the validity of the property on the client-side - mostly a (javascript) method name to be called during validation and some parameters needed to evaluate property within that method.
When we do this, this is the updated validator class will look like:
<?prettify lang=cs linenums=true?>
<pre class="prettyprint">[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)]
sealed public class MagicNumberAttribute : ValidationAttribute, IClientValidatable {
// constructor to accept the comparison property name
public MagicNumberAttribute(string thirtySevenProperty) : base() {
if (thirtySevenProperty == null) {
throw new ArgumentNullException("thirtySevenProperty");
}
ThirtySevenProperty= thirtySevenProperty;
}
// property to store the comparison field name
public string ThirtySevenProperty{ get; private set; }
protected override ValidationResult IsValid(object value, ValidationContext validationContext) {
// get property info of the "ThirtySevenProperty"
PropertyInfo thirtySevenPropertyInfo = validationContext.ObjectType.GetProperty(ThirtySevenProperty);
// check if that property exists / valid
if (thirtySevenPropertyInfo == null) {
return new ValidationResult(String.Format(CultureInfo.CurrentCulture, "UNKNOWN PROPERTY", ThirtySevenProperty));
}
// get the value of the property
object thirtySevenPropertyValue = thirtySevenPropertyInfo.GetValue(validationContext.ObjectInstance, null);
// do comparison and return validation result with error if not equal
if (!Equals(value, thirtySevenPropertyValue)) {
return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
}
// return null if everything is ok
return null;
}
public IEnumerable<ModelClientValidationRule$gt; GetClientValidationRules(ModelMetadata metadata, ControllerContext context) {
yield return new ModelClientValidationEqualToRule(this.ErrorMessage, OtherProperty);
}
}
</pre>
In the code above, I am reusing an existing validation rule "ModelClientValidationEqualToRule" which will connect with "equalto" jQuery client-validator. This is a pre-existing rule, so at this point, we can just run this and it should work - no more code needed. <br />
<br />
In the next post, I will go over on how to return our own custom ModelClientValidationRule and how to create a corresponding javascript client-side validator.
<br />
<br />
Additional reading:
<ul>
<li><a href="http://setiabud.blogspot.com/2013/03/creating-custom-validation-attribute.html">Creating Custom Validation Attribute</a>
</li>
<li><a href="http://setiabud.blogspot.com/2013/03/in-last-post-i-went-through-steps-to.html">
Creating Custom Validation Attribute With Dependency To Other Property</a>
</li>
<li><a href="http://setiabud.blogspot.com/2013/03/custom-validation-attribute-client-side_17.html">Custom Validation Attribute - Client Side Enabled With Custom Rule</a></li>
<li><a href="http://setiabud.blogspot.com/2013/03/creating-validation-adapter-for.html">Creating Validation Adapter for Validation Attribute</a></li>
</ul>
</div><div class="blogger-post-footer"><script type="text/javascript"><!--
google_ad_client = "pub-0606803324275047";
/* ContentAds */
google_ad_slot = "3381325551";
google_ad_width = 468;
google_ad_height = 60;
//-->
</script>
<script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js">
</script></div>Johannes Setiabudihttp://www.blogger.com/profile/14347781769200608947noreply@blogger.com0tag:blogger.com,1999:blog-6170125458717784512.post-19886398827727579872013-03-08T23:59:00.000-05:002013-03-29T16:27:22.935-04:00Creating Custom Validation Attribute With Dependency To Other PropertyIn the last post, I went through the steps to make a custom validation attribute - if you missed it, check it out here:
<a href="http://setiabud.blogspot.com/2013/03/creating-custom-validation-attribute.html">Creating Custom Validation Attribute</a>. Now let's say we want to make our validator to look up on the value "37" from another property in our class (instead of hard-coding it). How do we do that? With the help of reflection, we can do that quite easily. <br />
<div class="fullpost">
Here is our modified class:
<?prettify lang=cs linenums=true?>
<pre class="prettyprint">public class MyClass {
// ... more code
[MagicNumber("ThirtySeven", ErrorMessage = "Sum is not correct")]
public int MyData { get; set; }
// ... more code
public int ThirtySeven {
get { return 37; }
}
// ... more code
}
</pre><br />
In our new custom validator, we override a different IsValid method (the original one is commented out):
<?prettify lang=cs linenums=true?>
<pre class="prettyprint">
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)]
sealed public class MagicNumberAttribute : ValidationAttribute {
// constructor to accept the comparison property name
public MagicNumberAttribute(string thirtySevenProperty) : base() {
if (thirtySevenProperty == null) {
throw new ArgumentNullException("thirtySevenProperty");
}
ThirtySevenProperty= thirtySevenProperty;
}
// property to store the comparison field name
public string ThirtySevenProperty{ get; private set; }
// public override bool IsValid(object value) {
// var num = int.Parse(value.ToString());
// return num == 37;
// }
protected override ValidationResult IsValid(object value, ValidationContext validationContext) {
// get property info of the "ThirtySevenProperty"
PropertyInfo thirtySevenPropertyInfo = validationContext.ObjectType.GetProperty(ThirtySevenProperty);
// check if that property exists / valid
if (thirtySevenPropertyInfo == null) {
return new ValidationResult(String.Format(CultureInfo.CurrentCulture, "UNKNOWN PROPERTY", ThirtySevenProperty));
}
// get the value of the property
object thirtySevenPropertyValue = thirtySevenPropertyInfo.GetValue(validationContext.ObjectInstance, null);
// do comparison and return validation result with error if not equal
if (!Equals(value, thirtySevenPropertyValue)) {
return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
}
// return null if everything is ok
return null;
}
}
</pre>
<br />
That's it! <br />
Additional reading:
<ul>
<li><a href="http://setiabud.blogspot.com/2013/03/creating-custom-validation-attribute.html">Creating Custom Validation Attribute</a>
</li>
<li><a href="http://setiabud.blogspot.com/2013/03/custom-validation-attribute-client-side.html
">Custom Validation Attribute - Client Side Enabled!</a>
</li>
<li><a href="http://setiabud.blogspot.com/2013/03/custom-validation-attribute-client-side_17.html">Custom Validation Attribute - Client Side Enabled With Custom Rule</a></li>
<li><a href="http://setiabud.blogspot.com/2013/03/creating-validation-adapter-for.html">Creating Validation Adapter for Validation Attribute</a></li>
</ul>
</div>
<div class="blogger-post-footer"><script type="text/javascript"><!--
google_ad_client = "pub-0606803324275047";
/* ContentAds */
google_ad_slot = "3381325551";
google_ad_width = 468;
google_ad_height = 60;
//-->
</script>
<script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js">
</script></div>Johannes Setiabudihttp://www.blogger.com/profile/14347781769200608947noreply@blogger.com1tag:blogger.com,1999:blog-6170125458717784512.post-62920004933946591072013-03-03T23:35:00.000-05:002013-03-29T16:27:16.702-04:00Creating Custom Validation AttributeIn this post, I will outline steps in making custom validation attribute. This is the first post of a series, which eventually will go all the way to making the validation to be able to evaluated in the client side. But, let's not get too far ahead - first we need to make our custom validator. <br />
<div class="fullpost">
In this example, I have a simple validator that need to evaluate whether the value of the field "MyData" is equal to the "37". <br />
<br />
Our class:
<br />
<?prettify lang=cs linenums=true?>
<pre class="prettyprint">public class MyClass {
// ... more code
[MagicNumber(ErrorMessage = "Magic Number is not correct")]
public int MyData { get; set; }
// ... more code
}
</pre>
<br />
Our custom validator:
<br />
<?prettify lang=cs linenums=true?>
<pre class="prettyprint">[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)]
sealed public class MagicNumberAttribute : ValidationAttribute {
public override bool IsValid(object value) {
var num = int.Parse(value.ToString());
return num == 37;
}
}
</pre>
<br />
Done. Simple. <br />
Additional reading:
<ul><li><a href="http://setiabud.blogspot.com/2013/03/in-last-post-i-went-through-steps-to.html">Creating Custom Validation Attribute With Dependency To Other Property</a></li>
<li><a href="http://setiabud.blogspot.com/2013/03/custom-validation-attribute-client-side.html
">Custom Validation Attribute - Client Side Enabled!</a>
</li><li><a href="http://setiabud.blogspot.com/2013/03/custom-validation-attribute-client-side_17.html">Custom Validation Attribute - Client Side Enabled With Custom Rule</a></li>
<li><a href="http://setiabud.blogspot.com/2013/03/creating-validation-adapter-for.html">Creating Validation Adapter for Validation Attribute</a></li>
</ul>
</div><div class="blogger-post-footer"><script type="text/javascript"><!--
google_ad_client = "pub-0606803324275047";
/* ContentAds */
google_ad_slot = "3381325551";
google_ad_width = 468;
google_ad_height = 60;
//-->
</script>
<script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js">
</script></div>Johannes Setiabudihttp://www.blogger.com/profile/14347781769200608947noreply@blogger.com0tag:blogger.com,1999:blog-6170125458717784512.post-13317921191444923222012-12-11T22:15:00.000-05:002012-12-14T08:51:41.221-05:00Prepaid Cellphone Services (update)In July, I wrote about going PREPAID for my cellphone service - in which I mentioned T-Mobile Monthly 4G plan. IN the past several months, I have done quite an extensive research about available prepaid service, from just minutes and messages to all unlimited. This post is a highlight or summary of what I found and my recommendations.<br />
<br />
<h3>
<u>Straight Talk</u></h3>
<a href="http://www.straighttalk.com/">Straight Talk</a> is probably providing the best value for prepaid phone service on the market right now in US - with<b> <u>$45 a month for unlimited voice, messages, and data</u>*</b>. This is probably my choice in January when I am done with my T-Mobile contract. <br />
<div class="fullpost">
<br />
Straight Talk Wireless' parent company is TracFone Wireless - which is the largest prepaid service provide in US. TracFone mostly offers basic services such as voice only or voice and messages only. Straight Talk is different that it's main service is about <u><b>unlimited* voice, messages, and data for $45 a month</b></u>. The data part is not truly unlimited - it is 2-5GB with 3G speed with some restrictions such as data plan cannot be used to tether, cannot be used with BB service, no P2P, etc.<br />
<br />
<u><b>Straight Talk uses GSM bands from both T-Mobile and ATT</b></u>. So what this means is that you can bring your own ATT-locked phone and asked for ATT-based SIM from Straight Talk or bringing your own T-Mobile-locked phone and ask for T-Mobile based SIM. If you have a T-Mobile based phone, you will get the T-Mobile 4G (HSDPA+) with Straight Talk. ATT will only give you 3G.<br />
<br />
<u><b>Using both bands also means larger selection of handsets</b></u>. If you want to keep your Lumia 900 from ATT, you can. If you want to use the Galaxy S3 from T-Mobile, you can. If you want to use the new Nokia Lumia 920 from ATT, you can. No need to unlock the device - just ask for specific SIM card based on the phone.<br />
<br />
You can <a href="https://www.straighttalk.com/secure/ServicePlans">pay in advance for several months or even a year</a> - which will give you some savings. For example, if you buy a year plan, it will cost you $495 (instead of 12 * $45). <br />
<br />
There are instances where MMS won't work with Straight Talk. There is also a short configuration setting mads that you will need to do once the SIM is installed. Nothing hard or requires unlocking etc - see <a href="http://www.straighttalksim.com/support.php">here</a>. <br />
<br />
<h3>
<u>T-Mobile</u></h3>
With T-Mobile, to get the similar package like Straight Talk, you will need to pay $60 per month. But T-Mobile provides more options - see my post about T-Mobile prepaid service <a href="http://setiabud.blogspot.com/2012/07/i-may-be-going-prepaid-cellular-plan.html">here</a>. The killer awesome deal from T-Mobile is the $30 per month (web only or from Walmart) which gives you 100 minutes, unlimited messages, and 5GB of data. This is actually perfect for me and would have gone for it except for the causes below.<br />
<br />
T-Mobile coverages is quite lacking compared to ATT or Verizon. I go to several places in a year that my current T-Mobile phone do not get coverage at all - while my ATT friends get their 3G.<br />
<br />
Secondly, T-Mobile handset selection is limited, especially compared to Straight Talk, which gives you the flexibility to choose handsets from BOTH ATT and T-Mobile. <br />
<br />
Also, T-Mobile customer service has been awful for the past 2 years or so. <br />
<br />
<h3>
<u>Virgin Mobile and Cricket Wireless</u></h3>
<a href="http://www.virginmobileusa.com/cell-phone-plans/beyond-talk-plans/overview/">Virgin Mobile</a> (running on Sprint network) probably provides the best pricing with $35 per month for 300 minutes, unlimited messages and data* (2.5 GB). This would have been the perfect plan for me. But then, to go unlimited everything, it would have cost you $55 - which is still more expensive than Straight Talk.<br />
<br />
Cricket also has $45 a month for 1000 minutes with unlimited text and data (1GB). To get 2.5GB data, the cost is $65 per month. <br />
<br />
Virgin & Cricket also has very limited selection of phone (Sprint phones) - but it has the iPhone 4 and iPhone 4S options. Secondly, Sprint's 3G (or 4G) is the slowest.<br />
<br />
<h3>
<u>Others</u></h3>
<a href="http://www.boostmobile.com/shop/plans/monthly-unlimited/">Boost Mobile</a> provides unlimited voice, text, and data for $50 per month, but like Straight Talk, if you pay several months in advance then you will get a discount. <br />
<br />
Verizon provides the best coverage for 3G, but it is also the most expensive. It will cost you $50 per month for unlimited text, voice, and web* on a basic-dumb-phone. If you have a smartphone, it will <a href="http://www.verizonwireless.com/wcms/consumer/shop/prepaid.html#chooseaplan">cost you $80 per month</a>. <br />
<br />
<a href="http://www.att.com/shop/wireless/plans/prepaidplans.html">ATT GoPhone</a> costs $65 per month with 1GB of 3G data. <br />
<br />
<br />
<br /></div>
<div class="blogger-post-footer"><script type="text/javascript"><!--
google_ad_client = "pub-0606803324275047";
/* ContentAds */
google_ad_slot = "3381325551";
google_ad_width = 468;
google_ad_height = 60;
//-->
</script>
<script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js">
</script></div>Johannes Setiabudihttp://www.blogger.com/profile/14347781769200608947noreply@blogger.com0tag:blogger.com,1999:blog-6170125458717784512.post-84019345669662049342012-11-18T20:17:00.000-05:002012-11-19T08:07:43.703-05:00Microsoft Surface<div class="separator" style="clear: both; text-align: center;">
<a href="http://4.bp.blogspot.com/-fY0yvBf0zRQ/UKouqzW3Z6I/AAAAAAABCQQ/7XRKJecTnJw/s1600/surface_overall.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" height="149" src="http://4.bp.blogspot.com/-fY0yvBf0zRQ/UKouqzW3Z6I/AAAAAAABCQQ/7XRKJecTnJw/s320/surface_overall.png" width="320" /></a></div>
Several months ago, my trusty DELL laptop that is provisioned from my employer died. Since I have been working on a project at a client that provisions me with a desktop, I have not been rushing to get a replacement (although I did ask for a replacement). But since the contract mandates that any work I do must be done on the client's facility, so I am all equipped with what I need to perform my work and a work laptop would be superfluous.<br />
<br />
But there is also a need for a mobile device for me - different client assignments, VPN to the office, RDP to my development VMs, creating documents and presentations, etc. I have a smartphone that fulfill most of my email and social networking needs, but my smartphone is far from the quintessential device to create content and be productive (such as creating a Power Point presentation or writing a blog post like this). <br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="http://1.bp.blogspot.com/-uwu6uvei-p4/UKZc8jd3BRI/AAAAAAABCPg/v-bqwVD8moQ/s1600/home_office.jpg" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" height="240" src="http://1.bp.blogspot.com/-uwu6uvei-p4/UKZc8jd3BRI/AAAAAAABCPg/v-bqwVD8moQ/s320/home_office.jpg" width="320" /></a></div>
I am also more of a desktop guy. I prefer to have a beefy desktop to do
most of my work. At home, I have desktop with quad core CPU, 8GB of RAM, 256GB SSD, plenty of data storage, decent video card to power my three monitors setup (all 24 inches, 1920x1200). You can see my home setup on the picture on the left - where I have my desktop powering the monitors on the left. The Surface was connected to the smaller monitor (21") on the left.<br />
<br />
Trying to meet the need between the desktop and the smartphone, there are several ideas that come to mind: a huge smartphone (like Galaxy Note), a Chromebook, an ultra-book, or a tablet. But, none of them really fit with my needs or budget - until I heard about Microsoft Surface. I went to the closest store in Cleveland, OH to try and test the Microsoft Surface the day it came out on October 26th 2012. I liked it, bought it, and have been using it pretty much daily ever since. <br />
<br />
So why Surface? Why not those other options? Macbook Air? Chromebook? iPad? Galaxy Tab? Nexus 7?<br />
<br />
<h3>
<u>TL;DR </u></h3>
Microsoft Surface is fantastic. It fits my needs awesomely with the correct price point. It provides me the tools to be productive (like creating this lengthy blog post or creating a Power Point presentation) but yet still allows me to have the occasional entertainment and media consumption such as games (WORDAMENT, Angry Birds), movies (XBOX Movies, Hulu), etc. It is not the be-all and end-all laptop or tablet - but it is certainly much more capable than iPad, Chromebook, or any Android tablets I have tried.<br />
<br />
<div class="fullpost">
<h3>
<u>OFFICE SUITE</u></h3>
I owned a Galaxy Tab and used Nexus 7 quite extensively (before giving it to the office for Android development purpose). While the Tab and the Nexus 7 are nice consumption devices - they are really lacking in productivity tools. Quick Office, Google Docs (and others) are quite nice in their own rights, but they are not Microsoft Office. My experience in using them left me longing for the Office experience.<br />
<br />
The Surface comes with Office 2013 out of the box. It only has Word, Excel, Power Point, and OneNote. Now these applications are not striped down version of Word or Excel, etc - but they are the full version, just like the regular Office that I have on my desktop. Yes, it does not have Outlook or Access, but the essential four is enough for me. Just by that virtue alone - the Surface has become the best tablet for productivity.<br />
<br />
My mobile device does not need to be beefy - but it has to be able to support Office - and Surface does that (and more). This is also one of the reason why Chromebook is not enough - it runs great for most of my need (email, browsing, searches, remote desktop, even Office Web App), but falls short for my power user need for Office. <br />
<br />
<h3>
<u>TOUCH/TYPE COVER</u></h3>
The cover for Surface doubles as a keyboard as well - which is awesome.I have a cover for my Tab, which also functions as a stand. But then if I want to use a keyboard dock, I have to take the Tab out of the cover and dock it. Once done, I have to un-dock it and put it back into its cover. Plus, ever seen a Galaxy Tab keyboard/dock? It's ugly and thick.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="http://2.bp.blogspot.com/-TqLqJVOcEIs/UKZeSdjnXpI/AAAAAAABCPw/UGai8hHLIAE/s1600/surface_touch.jpg" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" height="221" src="http://2.bp.blogspot.com/-TqLqJVOcEIs/UKZeSdjnXpI/AAAAAAABCPw/UGai8hHLIAE/s320/surface_touch.jpg" width="320" /></a></div>
The Touch Cover is thin (3mm), acts as a cover, water-repellant, can be folded back when used as a tablet (without removing it), strong magnetically attached, and looks awesome. It does take some getting used to during my first day or so using it - but then after a while, I can type probably 85%+ speed on it (assuming 100% is my full-keyboard speed).<br />
<br />
The Type Cover is also thin, but thicker than the Touch Cover (6mm). It also can be folded back, magnetically attached, only comes in 1 color: black with gray back. With it I can probably type fairly close to full speed. Both covers are reversible and using very strong magnetic connection - so strong that I can hang my Surface upside down holding the cover without it falling off. <br />
<br />
If you hate the small keyboard or the feeling of it - you can still plug in your existing (mechanical) keyboard via USB - and it will always work. <br />
<br />
<h3>
<u>BATTERY LIFE</u></h3>
<a href="http://4.bp.blogspot.com/-OQ5Ce1J5iYw/UKZdcsGd3JI/AAAAAAABCPo/MkX68Qz6YwM/s1600/surface_office.jpg" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" height="240" src="http://4.bp.blogspot.com/-OQ5Ce1J5iYw/UKZdcsGd3JI/AAAAAAABCPo/MkX68Qz6YwM/s320/surface_office.jpg" width="320" /></a>The battery life for the Surface is great - pretty similar to iPad, about 9 hours plus. So basically it can easily last the whole day. I brought mine to the office last week and used it pretty much from the morning (around 9am) until end of day (around 4:30pm) without connecting it to its charger and with probably 10% left at the end of the day. This was using the Surface connected to an external monitor, with a USB mouse, and used to connect to a VM via remote desktop all day. You can see the setup in the picture on the right. <br />
<br />
This kind of battery life is pretty awesome because that means that I don't have to bring my charger every time. I can feel securely that it won't be running out of juice and it will last the whole day - and eventually connect it to the charger at night.<br />
<br />
The charger itself is great and it charges very very quickly. I'd say within a couple of hours or so - from almost empty to fully charged. I was certainly benefited from this when I had to charge my Surface on a rush before a trip. I don't know how many times I wished my phone or Tab or laptop would charge faster during a layover or at a friend's house or on the go. <br />
<br />
<h3>
<u>EXTERNAL CONNECTIONS and KICKSTAND</u></h3>
The Surface also has a micro HDMI out port and a USB port. This means I can connect my monitor if needed (like when I am at the office), connect it to a projector for presentation, connect thousands of peripherals (such as my phone, mice, keyboard, printers, etc). The USB port is really awesome - I don't know how many times I wished there is a USB port for my Tab or Nexus 7 - so I can print or connect a keyboard or even a phone or camera to transfer files.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="http://3.bp.blogspot.com/-mlxMwNwRK-8/UKZk1u-CgeI/AAAAAAABCQA/RiZozdfL1-U/s1600/surface_home.jpg" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" height="240" src="http://3.bp.blogspot.com/-mlxMwNwRK-8/UKZk1u-CgeI/AAAAAAABCQA/RiZozdfL1-U/s320/surface_home.jpg" width="320" /></a></div>
In my "docked" setup at home, I am connecting both the USB and the micro HDMI. The USB connects to a USB hub which then connects my external keyboard, mouse, card-reader (built-in to the monitor), and printer (not seen, under the desk). <br />
<br />
The Surface also has a micro-SD slot, so you can easily expand your space by just adding a micro-SD card - which is not an option for iPad or my Tab.<br />
<br />
The kick-stand is absolutely-without-a-doubt-ultra-useful. In my Tab or my friends' iPad, there are no built-in stand. I thought the iPad cover that folds into a stand was brilliant, but the built-in kick-stand for the Surface is more awesome - especially when it is being used in the productive mode like a laptop. <br />
<br />
<h3>
<u>MULTIPLE USER ACCOUNTS</u></h3>
Unlike a smartphone, where single account suffice, I need a mobile productivity tool that can be used with multiple user accounts. I want my emails to be separated from my wife's, my gamer score to be left alone by my son, and my setup not to be messed with. This feature allows me to customize my user experience according to me needs and my wife to her needs, and still allow a guest account for everybody else. <br />
<br />
Android supports that with the new 4.2 JellyBean update - but that will never come to my Tab. For iPad - this feature is non-existent. <br />
<br />
The fact that I am also a Windows user on my desktop, this
also means that my setup roams (using Microsoft Account), my music
selection & playlists roam, etc - which is nice indeed.<br />
<br />
<h3>
<u>OTHERS and NEED IMPROVEMENT</u></h3>
There are other things that make the Surface nice, such as the XBOX integration with Smartglass, XBOX music, Netflix, Hulu+, etc. Those are nice - but I'd say that those things are bonuses and not the main reason that attracted me to the Surface.<br />
<br />
The price point for the Surface is also right on. I think spending $1,000 for a Macbook Air (or more for Pro) for what I need is unnecessary and heedless spending. It is the same price point of an iPad/Galaxy Tab, but double the storage, and much much more productive-capable. <br />
<br />
There are several things that Microsoft can still improve. The first one is related to my experience with the Touch Cover - where the seam came lose near the connector within 2 days after purchase. MS Support took care of me (with the help of Mr. Sinofsky via Twitter), but it was still disappointing that a product needs to be replaced that soon.<br />
<br />
The magnetic connection to the charger is also not that great. It works awesomely when connected properly, but it is a bit of a pain to get it connected and won't snap with assurance (unlike the Covers) without really paying attention. The indicator light helps, but I wish it would have been easier. <br />
<br />
The Windows Store needs more apps. It has most of the apps that I need, but I think the ecosystem will get better with more apps. More games, more productivity tools, more niche apps. <br />
<br />
<h3>
<u>IS IT FOR YOU?</u></h3>
Well, isn't it the question? Depending on your needs and what you already invested, the answer can be "yes" or "no".<br />
<br />
Let's do the "NO"s first:<br />
<ul>
<li>If you already have a recent ultra-book (like Lenovo X1/X220/etc, ASUS Zenbook series, etc). I'd just upgrade your ultra-book to Windows 8 and be done with it. </li>
<li>If you are looking to have one-powerful-machine-to-do-them-all. If this is your need (running Photoshop, doing software dev, running VMs, playing Starcraft 2, etc) then get a robust powerful laptop instead. The Surface will never be a full laptop replacement. <b>Or wait for the Surface PRO</b>. </li>
<li>If you are already heavily invested in Apple lines (Macbook, iPad, iPhone with all their accessories). Unless you really want to "switch". </li>
</ul>
"YES" is a good answer if:<br />
<ul>
<li> Your needs are similar to mine: no laptop and have other computer to run other things, need MS Office, casual gamer, with no or little investments in other tablets. I think you'll love the Surface like I do. </li>
<li>Want to update or replace your old netbook and your needs are quite basic, such as email and browsing, social (Facebook, Twitter), MS Office. There is no sense of wasting $1,400 for a Macbook Pro or $1,000 for a Macbook Air if that's all pretty much you need your computer to do. </li>
</ul>
"Maybe" is in order:<br />
<ul>
<li>If you are a gamer and primarily looking for a casual gaming device. An iPad or
Nintendo 3DS or PSP Vita is probably better for that - or even an
Android tablet like the Nexus 7. This of course can change as the Windows Store are getting more and more apps/games. </li>
<li>If you are a heavy XBOX 360 user. Smartglass is awesome and being able
to watch a movie on your XBOX and then continue it on your Surface is
cool. Or getting extra contents of the movie you are watching, the games
you are playing, etc. But, Smartglass app for iOS, Android, and
WindowsPhone should be able to do the same thing. </li>
<li>If you want to switch from Apple to Windows. </li>
<li>The rest of all other reasons that you can think of. </li>
</ul>
<br />
<ul>
</ul>
</div>
<div class="blogger-post-footer"><script type="text/javascript"><!--
google_ad_client = "pub-0606803324275047";
/* ContentAds */
google_ad_slot = "3381325551";
google_ad_width = 468;
google_ad_height = 60;
//-->
</script>
<script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js">
</script></div>Johannes Setiabudihttp://www.blogger.com/profile/14347781769200608947noreply@blogger.com2tag:blogger.com,1999:blog-6170125458717784512.post-19468116760635874412012-10-30T21:10:00.000-04:002013-03-21T00:14:44.169-04:00Nest Thermostat<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="http://www.nest.com/inside-and-out/" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" src="http://d2pupd7a6asjpt.cloudfront.net/images/inside_and_out/nest_face-539bc3d8.jpg" /></a></div>
I heard about the Nest thermostat since last year and followed its development online through their blog and tech news sites. At the time, my digital thermostat was running fine, but it is always a pain to set/adjust, change the schedule, and non-intuitive - so my wife and my friends would usually ask me to adjust the temperature setting (instead of doing it themselves). So since that point on I have been on the lookout for a "better" thermostat and preferably an "internet connected" one. When I heard about pre-ordering a Nest, I did it right away and bought it when it came out last year. <br />
<br />
Nest was not cheap ($250), especially compared to the typical programmable thermostat you find at the hardware store (~$75). Most people will say that "it's just a thermostat - why get an expensive one?" - and they are right in a way that the benefit is not that apparent right away. But after using it about a week or so, I never looked back.<br />
<br />
So what sold me in the first place? After a year or so using it, what are my thoughts about it? This blog post will answer both questions.<br />
<br />
<div class="fullpost">
<h3>
<a href="http://4.bp.blogspot.com/-BbkHIMuI0_U/UJE7xALWSqI/AAAAAAABCOs/oZ1p7sDJnlw/s1600/nest_install.PNG" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" height="200" src="http://4.bp.blogspot.com/-BbkHIMuI0_U/UJE7xALWSqI/AAAAAAABCOs/oZ1p7sDJnlw/s200/nest_install.PNG" width="183" /></a><u>Top notch website</u> </h3>
Have you ever been to the Nest's website? I urge you to go there - <a href="http://www.nest.com/">http://www.nest.com</a>. Not only it is very well designed and easy to navigate - but it also absolutely informative. After reading some articles and watching some videos (all which are well made), it really answers all my questions and more. The videos put me at ease about doing a <a href="http://www.nest.com/installation/">self installation</a> or whether Nest will work my current furnace/AC with their compatibility checker.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="http://3.bp.blogspot.com/-MpbJEnQLhvI/UJE7zu1ia9I/AAAAAAABCO8/IFL9nlfNPZ4/s1600/nest_saving.PNG" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" height="100" src="http://3.bp.blogspot.com/-MpbJEnQLhvI/UJE7zu1ia9I/AAAAAAABCO8/IFL9nlfNPZ4/s200/nest_saving.PNG" width="200" /></a></div>
Nest's website also explain how will Nest help you save energy consumption. So instead of some nebulous magic, Nest spells it out in its <a href="http://www.nest.com/blog/">blog</a> and <a href="http://www.nest.com/saving-energy/">articles</a>. <br />
<br />
<a href="http://4.bp.blogspot.com/-PgpPoFIjfvA/UJE7x1nVh_I/AAAAAAABCO0/_T2gTguJ-AI/s1600/nest_living.PNG" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" height="113" src="http://4.bp.blogspot.com/-PgpPoFIjfvA/UJE7x1nVh_I/AAAAAAABCO0/_T2gTguJ-AI/s200/nest_living.PNG" width="200" /></a>There is also this "Living with Nest" page that pretty much explains how Nest actually work with your living habit, from the first day, the first week, first month, and eventually where Nest will stay out of your way and learn to understands your habits and adjust to them.<br />
<br />
<h3>
<u>Killer Nest Web App</u></h3>
<a href="http://1.bp.blogspot.com/-Von42L1WPmY/UJE7u-Bc8nI/AAAAAAABCOk/CXVfl5gANX4/s1600/nest_home.PNG" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" height="320" src="http://1.bp.blogspot.com/-Von42L1WPmY/UJE7u-Bc8nI/AAAAAAABCOk/CXVfl5gANX4/s320/nest_home.PNG" width="263" /></a>Nest also has a web app. So once you connect your Nest
to your WiFi, register it and create an account, you can actually manage
your Nest from anywhere in the world with an internet connection. <br />
<br />
This is a killer feature. I can be at the office or my friend's place and lower down the temperature at my house or even turn it off. I can pre-heat my home on my way back from vacation or cool it down, etc.<br />
<br />
The web app also allows you to manage your Nest settings such as Auto-Away & Away Temperatures, Schedule, Learning, Lock, etc. No more fiddling directly in the thermostat once you got Nest connected to the internet. Everything can be done via the web admin tool.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="http://2.bp.blogspot.com/-tUl4kf60Q7Y/UJE_NKGM60I/AAAAAAABCPI/nDf3pLpMXKc/s1600/nest_energy.PNG" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" height="91" src="http://2.bp.blogspot.com/-tUl4kf60Q7Y/UJE_NKGM60I/AAAAAAABCPI/nDf3pLpMXKc/s200/nest_energy.PNG" width="200" /></a></div>
The Web App also shows you the energy usage for the last 10 days. If you are saving energy, Nest will give you a "leaf" - which is also showed in the energy usage screen (a bit of gamification for energy saving). This energy calculation is also adjusting depending on the temperature in your area as well as any adjustments you make during that day. In the detail view, it shows the time of the day when the heating/AC unit is working and the sum of hours for the whole day. This is really awesome in helping me out in saving energy - thus saving money. This feature along has helped and motivated me and my family to adjust our habit to save energy. <br />
<br />
If you have multiple Nests in your home (to manage different units for multiple rooms etc), the web app also will identify each Nest individually, so you can control each of them independently.<br />
<br />
I have to say that their website and web-support is superb. Probably the best I have seen and experienced. Sometimes I could not help showing it of to my friends. You can read more about their <a href="http://support.nest.com/article/Exploring-the-Nest-Web-app">Web App here</a>. <br />
<br />
<h3>
<u>Mobile device support</u></h3>
<div style="text-align: left;">
<a href="http://support-assets.nest.com/images/000001144/mobile-mode.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" src="http://support-assets.nest.com/images/000001144/mobile-mode.png" /></a></div>
Nest also support mobile devices: iPhone, iPad, Android Phones and Tablets. Beyond the Web App, this is something that I probably use the most: adjusting temperature from my bed, or setting Away while on the road, or simply just checking the energy usage. The mobile app has most of the features that the web app has. Use the web app when sitting in front of the computer and use the mobile app when using the phone/tablet - awesome. <br />
<br />
It took Nest a while to support Android tablet, but thankfully, the web app can be accessed quite easily using FireFox or Chrome using the desktop mode. Now, Nest released an update that works for my Galaxy Tab. Now - I am a Windows Phone use, so if you are reading this blog, Nest - can you make a Windows Phone app? You can read more about the <a href="http://support.nest.com/article/Exploring-the-Nest-Mobile-app-for-your-smartphone-or-tablet">mobile app for Nest here</a>. <br />
<br />
<h3>
<u>Nest Monthly Energy Report</u></h3>
<div class="separator" style="clear: both; text-align: center;">
<a href="http://2.bp.blogspot.com/-BDOqgPru0x0/UJFofHMDQgI/AAAAAAABCPU/4sQhjxlIrWk/s1600/nest_report.PNG" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" height="320" src="http://2.bp.blogspot.com/-BDOqgPru0x0/UJFofHMDQgI/AAAAAAABCPU/4sQhjxlIrWk/s320/nest_report.PNG" width="304" /></a></div>
This is an unexpected feature/support that surprises me - in a very very good way. I did not know this for in the beginning but was pleasantly surprised when I found out about it. Basically, Nest sends you an email every month summarizing your Nest usage report, compared to previous month, your "leaf" earning, etc - as well as links to new blog posts, tips, and other Nest benefits.<br />
<br />
Unlike typical marketing emails or spams, this email from Nest is actually useful and beneficial to help you in using your Nest better and saving more energy. <br />
<br />
Getting this email is like getting a pad in the back about your energy saving and also motivate you to maximize your Nest instead of just simply leaving it as a programmable thermostat. It gives you a picture of your energy usage pattern and helps you to make adjustments to be more efficient.<br />
<br />
<h3>
<u>Software updates and support</u></h3>
I'd say that Nest has better software updates support and support in general than my Android devices. In 1 year, my Nest has had 2 software/firmware updates - both were feature packed updates (as well as fixes etc), huge improvements for the web app, and continuous updates for mobile device apps. All in all, excellent software and firmware support. <br />
<br />
Secondly, their website is filled with articles, videos, and how-tos on about anything you can think of about Nest - from installation, troubleshooting, compatibility, and apps. This combined with responsive <a href="mailto:support@nest.com">email</a> & <a href="http://www.twitter.com/nest">twitter</a> support, and phone support - I certainly feel taken care of and satisfied. My hats off to them. <br />
<br />
Oh yes, it comes with 2 years warranty too.
</div><div class="blogger-post-footer"><script type="text/javascript"><!--
google_ad_client = "pub-0606803324275047";
/* ContentAds */
google_ad_slot = "3381325551";
google_ad_width = 468;
google_ad_height = 60;
//-->
</script>
<script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js">
</script></div>Johannes Setiabudihttp://www.blogger.com/profile/14347781769200608947noreply@blogger.com0tag:blogger.com,1999:blog-6170125458717784512.post-38068527007694516512012-09-11T23:00:00.000-04:002012-09-12T11:02:38.060-04:00KOSS PortaPro Review<a href="http://2.bp.blogspot.com/-jESLGg12KTc/UFCbmxwFb2I/AAAAAAABCIE/uofmneAb99I/s1600/WP_000490.jpg" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" height="240" src="http://2.bp.blogspot.com/-jESLGg12KTc/UFCbmxwFb2I/AAAAAAABCIE/uofmneAb99I/s320/WP_000490.jpg" width="320" /></a>When my full-size headphone was snapped in half several years ago, I was scrambling to find a new one. I got a newer similar model from a local store for around $20 - but then after several months, its cable broke and became basically a mono headphone. A friend of mine then gave me a <a href="http://www.koss.com/en/products/headphones/on-ear-headphones/PortaPro__Porta_Pro_On_Ear_Headphone">KOSS portaPro</a>. The portaPro is an on-ear headphone, which I have never used before - since all of my headphones were always full-size ones. So understandably, I was very skeptical about it. But since my banged-up mono broken headphone was my only other option so I started using it. It has been about 3 years now since I started using the portaPro - and I am still using it as my trusty headphone.<br />
<br />
<div class="fullpost">
<h4>
<u><b>Awesome sound! </b></u></h4>
I still distinctly remember when I used it for the first time to listen to music (prior to that I was using it for listening to podcast), I was really blown away. The sound quality coming out from this seemingly dinky headphone is amazing. Yes, my old full-size headphones were cheap (less than $40) and obviously not BOSE or Beats or any of those fancy ones. But seriously, this portaPro was awesome in terms of sound quality. Clear and powerful bass, superb mid-range, balanced treble. I would say that the sound quality of this portaPro probably competes with the mid-range ($75-$150) full-size headphones (with no noise-cancelling). <br />
<br />
<h4>
<u><b>Cheap!</b></u></h4>
Since the portaPro was a gift, I did not know how much it cost - so I looked it up online on <a href="http://www.amazon.com/Koss-PortaPro-Headphones-with-Case/dp/B00001P4ZH/ref=sr_1_1?ie=UTF8&qid=1347460742&sr=8-1&keywords=portaPro">Amazon</a> - and it was cheap (or at least A LOT cheaper than those fancy headphones). It is only about $45 (or less) - which is only 1/4 of the price of Beats ($199), 1/7 from Bose ($299), or 1/2 of any typical Sony headphones (~$100). Then you say it's because it's an on-ear, of course it's cheap. Yes, that might be true - but, I have tried other on-ear headphones (Logitech, Sony, Panasonic, Phillips) and none of them rival the sound quality of the portaPro. Truly, I think the combination of the sound quality and the economic price hits the sweet spot for me. <br />
<br />
<h4>
<u><b>Small size</b></u></h4>
The advantage of having the portaPro vs a full-size headphone is the small size. It fits easily into my laptop bag, my travel bag, or even my wife's purse. This is something I could not do with my full-size headphone - which means if I decide to carry it, I practically have to have it hanging on my neck all the time if not using it. The portaPro also folds - makes it even smaller.<br />
<br />
<h4>
<u><b>Last longer than my older headphones</b></u></h4>
<div class="separator" style="clear: both; text-align: center;">
<a href="http://4.bp.blogspot.com/-qUklbLyrDBI/UFCbnjrzkFI/AAAAAAABCIM/ubkpiKe8KtM/s1600/WP_000491.jpg" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" height="320" src="http://4.bp.blogspot.com/-qUklbLyrDBI/UFCbnjrzkFI/AAAAAAABCIM/ubkpiKe8KtM/s320/WP_000491.jpg" width="240" /></a></div>
This portaPro probably takes a lot more abuse than my older headphones, since I carry it in more places, shoved into my bags repeatedly, taken into trips, etc. But still looks great, functions normally, and really no complains from me. It looks flimsy, but it seems to last forever.<br />
<br />
After 3 years, I had to replace the foaming pads that cover the drivers. The old foaming are torn. I bought the foaming on <a href="http://www.amazon.com/gp/product/B000O2KIMO/ref=oh_details_o01_s00_i02">Amazon</a> and they install easily - just remove the old foaming, put new ones one - took about 3 minutes and done. No glue, no stitching, no hassle.<br />
<br />
Now it feels like it's a new headphone again. <br />
<br />
So if you are in the market for a headphone, I suggest you check out KOSS portaPro. I totally love mine and I am guessing you will be pleasantly surprise by it.
</div><div class="blogger-post-footer"><script type="text/javascript"><!--
google_ad_client = "pub-0606803324275047";
/* ContentAds */
google_ad_slot = "3381325551";
google_ad_width = 468;
google_ad_height = 60;
//-->
</script>
<script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js">
</script></div>Johannes Setiabudihttp://www.blogger.com/profile/14347781769200608947noreply@blogger.com0