[Dev Tip] Wrap HttpPostedFileStream for unit test,…


using System.IO;
using System.Web;

namespace Hydra.Insurance.Web.Helpers
{
public class HttpPostedFileStreamWrapper : HttpPostedFileBase
{
private string contentType;
private string filename;
private Stream inputStream;

public HttpPostedFileStreamWrapper(Stream inputStream, string contentType = null, string filename = null)
{
this.inputStream = inputStream;
this.contentType = contentType;
this.filename = filename;
}

public override int ContentLength
{
get
{
return (int)inputStream.Length;
}
}

public override string ContentType
{
get
{
return contentType;
}
}

public override string FileName
{
get
{
return filename;
}
}

public override Stream InputStream
{
get
{
return inputStream;
}
}

public override void SaveAs(string filename)
{
using (var stream = File.OpenWrite(filename))
{
InputStream.CopyTo(stream);
}
}
}
}

[Dev tip] Per request lifetime management for UserManager class in ASP.NET Identity

Introduction

We recently released the 2.0.0-beta1 version of ASP.NET Identity. Learn more here by visiting this link. This is an update to 2.0.0-alpha1 and adds the two-factor auth feature along with a few bug fixes. To learn more about the Alpha release, please visit this link.

In this article I am going to explain the significance of the UserManager class in an application and some of the best practices when using it. I am going to use an MVC application that is created using VS 2013 RTM. In the Visual Studio 2013 RTM templates, which had the 1.0.0 version of ASP.NET Identity, we demonstrated directly instantiating the UserManager class as needed in the application. This approach had a few issues, which are explained further in this article. These have been fixed in 2.0.0-beta1.

Understanding Managers and Stores

ASP.NET Identity consists of classes called managers and stores. Managers are high-level classes which an application developer uses to perform operations in the ASP.NET Identity system, such as creating a user. Stores are lower-level classes that specify how entities, such as users and roles, are persisted. Stores are closely coupled with the persistence mechanism, but managers are decoupled from stores which means you can replace the persistence mechanism without disrupting the entire application.

The current article outlines the steps to configure the UserManager in the application. We will start with an application with Identity 1.0.0. We will migrate the solution to 2.0.0-beta1, and change the way UserManager is instantiated and used in the application. Additionally, with this approach it is possible to configure the properties on the UserManager, such as password length and complexity.

Create Application

In Visual Studio 2013 RTM create a new web application. Choose MVC

clip_image002

UserManager explained

Let us briefly look at how the UserManager class is used in the application. All user account management actions are defined in the AccountController class.

Code Snippet
  1. public AccountController()
  2.             : this(new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(new ApplicationDbContext())))
  3.         {
  4.         }
  5.         public AccountController(UserManager<ApplicationUser> userManager)
  6.         {
  7.             UserManager = userManager;
  8.         }
  9.         public UserManager<ApplicationUser> UserManager { get; private set; }

The controller has a UserManager property of type UserManager which is set in the constructor. The UserManager class takes in an instance of UserStore class which implements operations on the user that are persistence-specific. In our case, we have a UserStore class which implements these operations specific to EntityFramework. To persist data to/from the database the UserStore class takes in an instance of DBContext class. The UserStore class is defined in the Microsoft.AspNet.Identity.EntityFramework assembly.

Note: The rationale behind implementing this pattern is that if the developer wishes to store user information in the any other storage system (for example, Azure Table storage), all they have do is replace the ‘UserStore’ class with an Azure Table storage implementation. There would be no additional changes needed in the AccountController, and the existing application would function seamlessly.

The problem

In the current approach, if there are two instances of the UserManager in the request that work on the same user, they would be working with two different instances of the user object. An example for this would be using the one in the class property and instantiating one locally in the method under execution. In this scenario the changes made by either of them would not reflect the changes made by the other. Hence persisting these changes back to the database would lead to incorrect changes being made to the user object.

The same problem exists when you are trying to use the DBContext class in the application.

The solution

The solution to the above problem is to store a single instance of UserManager and DbContext per request and reuse them throughout the application. Since Identity hooks into the OWIN pipeline via cookie middleware, we can store the UserManager and DbContext in the OWIN context object and retrieve them as needed.

1. In the application created above, update the Identity packages to 2.0.0-beta1 from the NuGet feed. This can be done through the Manage Nuget packages window. The steps to update Nuget packages are explained here.

2. Instead of directly working with UserManager<T> class we can define a custom class, ApplicationUserManager that extends from UserManager<T>. In the project under the App_Start folder create a new file IdentityConfig.cs and add a new class ApplicationUserManager. The ApplicationUserManager should extend the UserManager class

Code Snippet
  1. public class ApplicationUserManager : UserManager<ApplicationUser>
  2.     {
  3.     }

3. The web application uses the new OWIN cookie middleware for the cookie-based authentication. During application start, the Configuration method in the Startup class is invoked, which configures all the middleware components registered in the application. In the MVC 5 template, the cookie middleware is configured through the ConfigAuth method defined in the Startup.Auth class.

Since we need to register the UserManager and DBContext class with the OWIN context during app start, we will add methods to do that in the ConfigureAuth method. The ‘CreatePerOwinContext<T>’ method is defined in the Microsoft.AspNet.Identity.Owin namespace. This method registers a static callback method which returns an instance of type <T>. This method is invoked once per request and used to obtain instance object which is used during the lifetime of the request.

4. To create a static callback method that returns an instance of DbContext , in the ApplicationDbContext class create a method as defined below

Code Snippet
  1. public static ApplicationDbContext Create()
  2.     {
  3.         return new ApplicationDbContext();
  4.     }

5. Similarly define a method in the ApplicationUserManager that returns an instance of the ApplicationUserManager.

Code Snippet
  1. public static ApplicationUserManager Create(IdentityFactoryOptions<ApplicationUserManager> options, IOwinContext context)
  2.         {
  3.             var manager = new ApplicationUserManager(new UserStore<ApplicationUser>(context.Get<ApplicationDbContext>()));
  4.             return manager;
  5.         }

In the constructor of the UserManager, we need to retrieve the instance of DbContext to configure the UserStore. We can get the instance of the object from the OwinContext using the ‘Get<ApplicationDbContext>’ method that in turn returns the single instance of DbContext class created using ApplicationDbContext.Create callback method.

6. Register these two callback methods in the ConfigureAuth method through the ‘CreatePerOwinContext’ method

Code Snippet
  1. public void ConfigureAuth(IAppBuilder app)
  2.         {
  3.         app.CreatePerOwinContext<ApplicationDbContext>(ApplicationDbContext.Create);
  4.         app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
  5.          .
  6.          }

7. Next we hook this up in the AccountController class. Change the constructor and the UserManager property on the class to type ApplicationUserManager

Code Snippet
  1. public AccountController()
  2.         {
  3.         }
  4.         public AccountController(ApplicationUserManager userManager)
  5.         {
  6.             UserManager = userManager;
  7.         }
  8.         private ApplicationUserManager _userManager;
  9.         public ApplicationUserManager UserManager
  10.         {
  11.             get;
  12.             private set;
  13.         }

8. In the set property of the UserManager we need can retrieve the UserManager instance from the OWIN context. For this we have an extension method provided in the Microsoft.AspNet.Identity.Owin namespace

Code Snippet
  1. public ApplicationUserManager UserManager
  2.         {
  3.             get
  4.             {
  5.                 return _userManager ?? HttpContext.GetOwinContext().GetUserManager<ApplicationUserManager>();
  6.             }
  7.             private set
  8.             {
  9.                 _userManager = value;
  10.             }
  11.         }

9. Run the application and verify that a local user can be created in the application.

10. Also in the application if we need to work with the DbContext object directly we can get the instance of the class from the OWIN context as mentioned earlier using the ‘Get<T>’ method

Code Snippet
  1. var dbContext = context.Get<ApplicationDbContext>();

Configuring UserManager properties

Another advantage of following this approach is that we can configure the UserManager properties when instantiating it in a single place. For example, the UserManager by default has a password validator that validates that the supplied password is of length 6 characters. We can change this to use the new password validator in 2.0.0-beta1 which checks for additional complexity in the supplied password during registration.

To do this, simply set the PasswordValidator property in the ‘Create’ method of the ApplicationUserManager with the new ‘PasswordValidator’ class

Code Snippet
  1. public ApplicationUserManager(UserStore<ApplicationUser> userStore)
  2.             : base(userStore)
  3.         {
  4.         }
  5.         public static ApplicationUserManager Create(IdentityFactoryOptions<ApplicationUserManager> options, IOwinContext context)
  6.         {
  7.             var manager = new ApplicationUserManager(new UserStore<ApplicationUser>(context.Get<ApplicationDbContext>()));
  8.             manager.PasswordValidator = new PasswordValidator
  9.             {
  10.                 RequiredLength = 10,
  11.                 RequireNonLetterOrDigit = true,
  12.                 RequireDigit = true,
  13.                 RequireLowercase = false,
  14.                 RequireUppercase = false,
  15.             };
  16.             return manager;
  17.         }

Here the PasswordValidator class is configured to verify that the supplied password has a non-alphanumeric character and a numeric character. Also the length of password should be of 10 characters length. Similarly other properties of the UserManager can be configured to be persisted through the context lifecycle.

Summary

This post shows how to get a per-request, single instance of the UserManager and DbContext classes from the OWIN context to be used throughout the application. This will form a base for additional blog posts outlining the new features in ASP.NET Identity 2.0.0-beta1.

I hope you have found this walkthrough useful. If you have any questions around ASP.NET Identity or find issues, please feel free to open bugs on the Identity Codeplex site, https://aspnetidentity.codeplex.com/ , leave comments on this blog, or ask questions or StackOverflow (tag: aspnet-identity). I can also be reached on twitter (@suhasbjoshi).

Ref: http://blogs.msdn.com/b/webdev/archive/2014/02/12/per-request-lifetime-management-for-usermanager-class-in-asp-net-identity.aspx

[Dev tip] ASP.NET MVC back button with html helper method


public static MvcHtmlString BackButton(this HtmlHelper helper, string buttonName = null, string @class = "btn btn-default")
{
if (string.IsNullOrEmpty(buttonName))
{
buttonName = Hydra.Common.I18n.ResourceConstants.Button_Back;
}

var previousURI = (System.Uri)helper.ViewContext.RequestContext.HttpContext.Request.UrlReferrer;
string result = "<a href=\"{0}\" class=\"{1}\">{2}</a>".FormatWith(previousURI, @class, buttonName);

return new MvcHtmlString(result);
}

 

on view

@Html.BackButton()

[Dev Tip] FluentAutomation for automated testing of Web Applications

Last week I was exploring today’s varied choices we have for Automated Browser Testing. There’s headless WebKit “browsers” like PhantomJS and cloud powered multi-browser testing tools like BrowserStack and SauceLabs.

Selenium is kind of the gold standard and offers not only a lot of “drivers” but also a lot of language bindings with which drive a browser. Sometimes browsers update so fast there can be some version incompatibilities with Selenium, but for the most part it works great once you’ve settled in.

One option I’ve been looking at is FluentAutomation. It’s a fluent automation API that supports Selenium as well as WatiN along with all their flavors and drivers. Since Fluient supports Selenium, that means you can use the Selenium ChromeDriver, IEDriver, Remote Web Driver or even the headless PhantomJS.FluentAutomation is on GitHub, of course, as well as on NuGet.

FluentAutomation has great (and growing) documentation and has adopted and interesting fluent style for it’s API.

Now, not everyone likes a “fluent” API so it may take a while to get used to. Often you’ll be doing things over many lines when it’s really just one line, for example, this is one line:

I.Open("http://automation.apphb.com/forms")
    .Select("Motorcycles").From(".liveExample tr select:eq(0)")
    .Select(2).From(".liveExample tr select:eq(1)")
    .Enter(6).In(".liveExample td.quantity input:eq(0)")
    .Expect
        .Text("$197.72").In(".liveExample tr span:eq(1)")
        .Value(6).In(".liveExample td.quantity input:eq(0)");

Notice the method chaining as well as the use of CSS selectors.

FluentAutomation also has the cool concept of a PageObject to take your potentially brittle scripts and give them more structure. PageObjects group your actions, expectations, and assertions and let you reuse code when a page appears in multiple tests.

For example you could have a high level test (this is XUnit, but you can use whatever you want):

public class SampleTest : FluentTest {
    public SampleTest() {
        SeleniumWebDriver.Bootstrap(SeleniumWebDriver.Browser.Chrome);
    }
    [Fact]
    public void SearchForFluentAutomation() {
        new BingSearchPage(this)
            .Go()
            .Search("FluentAutomation")
            .FindResultUrl("http://fluent.stirno.com/blog/FluentAutomation-scriptcs/");
    }
}

Then you can have separate PageObjects that have your own public methods specific to that page, as well as assertions you can reuse.

public class BingSearchPage : PageObject<BingSearchPage> {
    public BingSearchPage(FluentTest test) : base(test) {
        Url = "http://bing.com/";
        At = () => I.Expect.Exists(SearchInput);
    }
    public BingSearchResultsPage Search(string searchText) {
        I.Enter(searchText).In(SearchInput);
        I.Press("{ENTER}");
        return this.Switch<BingSearchResultsPage>();
    }
    private const string SearchInput = "input[title='Enter your search term']";
}
public class BingSearchResultsPage : PageObject<BingSearchResultsPage> {
    public BingSearchResultsPage(FluentTest test) : base(test) {
        At = () => I.Expect.Exists(SearchResultsContainer);
    }
    public BingSearchResultsPage FindResultUrl(string url) {
        I.Expect.Exists(string.Format(ResultUrlLink, url));
        return this;
    }
    private const string SearchResultsContainer = "#b_results";
    private const string ResultUrlLink = "a[href='{0}']";
}

You don’t have to be all structure and OO if you don’t want. You can just as easily write scripts with FluentAutomation and head in a different direction.

FLUENTAUTOMATION ALONG WITH SCRIPTCS = AUTOMATING YOUR BROWSER WITH C# SCRIPT

I’ve usually used Python with my Selenium scripts. I like being able to just make a text file and start scripting, then run, debug, continue, all from the command line. It feels simple and lightweight. Creating a DLL and running Unit Tests in C# usually comes later, as I can move faster with a “scripting language.”

You can do that with ScriptsCS as it gives you project-less C# that effectively is C# as scripting language. Combine this with FluentAutomation and you’ve potentially got the best of both worlds.

To install, first you need the Windows apt-get open source equivalent, the oddly-named and -spelledChocolatey. Then you get ScriptCS and the packages for FluentAutomation.

  • Install Chocolatey – one line installation here
  • Run “cinst ScriptCS” from your command line to use Chocolatey to install ScriptCS
  • Now, get the ScriptCS script packages for FluentAutomation like this:
    • scriptcs -install FluentAutomation.SeleniumWebDriver
    • scriptcs -install ScriptCs.FluentAutomation

Now, as a quick test, create a folder and put a text file called start.csx in it with just these contents:

var Test = Require<F14N>()
    .Init<FluentAutomation.SeleniumWebDriver>()
    .Bootstrap("Chrome")
    .Config(settings => {
        // Easy access to FluentAutomation.Settings values
        settings.DefaultWaitUntilTimeout = TimeSpan.FromSeconds(1);
    });
 
Test.Run("Hello Google", I => {
    I.Open(http://google.com);
});

Notice how there’s no namespace, no classes, no main. It’s just a script, except it’s using C#. You can change the “Chrome” to “IE” or “Firefox” as well, to play around.

Random: I love this Selenium feature, exposed by FluentAutomation…take screenshot!

// Take Screenshot
I.TakeScreenshot("LoginScreen");

If you don’t want ScriptCS, while it can act as a REPL itself, there is also the start of a dedicated FluentAutomation REPL (read–eval–print loop). This is basically a command prompt that lets you explore you app interactively and facilitates building your scripts. You can get the Repl as a Chocolatey package as well and just “cinst FluentAutomation.Repl”

You’ve got LOTS of choices in the world of automated testing. There’s so many choices that there’s just no good excuse. Pick a library, pick a language, and start automating your web app today.

RELATED LINKS

Ref: http://www.hanselman.com/blog/NuGetPackageOfTheWeekFluentAutomationForAutomatedTestingOfWebApplications.aspx

[Dev Tip] How to display a QR code in ASP.NET and WPF

I’ve half-jokingly said that there’s never a good reason to use a QR Code. However, I’m working on an MVP (minimally viable product) for a small startup with Greg Shackles and we actually have a good reason to use one. We have a mobile device, a Web Site, and a Windows Application, and a QR Code is a pretty quick way to move data between the mobile device and the other applications without the mess of Bluetooth pairing.

As I mentioned, we display the QR code on an ASP.NET website, as well as within a Windows app that happens to be written in WPF. The iPhone app uses C# and Xamarin.

There’s a great QR Code library called “ZXing” (Zebra Crossing) with ports in Java and also in C#. The C#/.NET one, ZXing.NETis a really fantastically well put together project with assemblies available for everything from .NET 2 to 4.5, Windows RT, Unity3D, Portable libraries and more. The site is filled with demo clients as well, although we didn’t find one for ASP.NET or WPF. No matter, it’s all just generating and showing PNGs.

I pulled in ZXing.NET from the NuGet package here, just install-package ZXing.Net.

HOW TO DISPLAY A QR CODE IN ASP.NET

If you’re generating a QR code with ASP.NET MVC, you’ll have the page that the code lives on, but then you’ll need to decide if you want to make an HTTP Handler that generates the graphic, like:

<img src="/path/to/httphandlerthatmakesQRcodepng">

or, you could take a different approach like we did, and embed the code in the HTML page itself.

Greg used an HTML Helper to output the entire image tag, including the inlined image, as in:

<img src="data:image/gif;base64,iVBORw0KG..." />

Images in HTML directly as Data URIs are super fun and I think, often forgotten. If you show one to the average web dev they’ll say “oh, ya…I knew about those, but never really used it.” In fact, Data URIs have been around for a LONG time. Learn more about them at DataUrl.net.

Here’s generating a QR Code within ASP.NET MVC from an HTML Helper:

public static class HtmlHelperExtensions
{
    public static IHtmlString GenerateRelayQrCode(this HtmlHelper html, string groupName, int height = 250, int width = 250, int margin = 0)
    {
        var qrValue = "whatever data you want to put in here";
        var barcodeWriter = new BarcodeWriter
        {
            Format = BarcodeFormat.QR_CODE,
            Options = new EncodingOptions
            {
                Height = height,
                Width = width,
                Margin = margin
            }
        };
        using (var bitmap = barcodeWriter.Write(qrValue))
        using (var stream = new MemoryStream())
        {
            bitmap.Save(stream, ImageFormat.Gif);
            var img = new TagBuilder("img");
            img.MergeAttribute("alt", "your alt tag");
            img.Attributes.Add("src", String.Format("data:image/gif;base64,{0}",
                Convert.ToBase64String(stream.ToArray())));
            return MvcHtmlString.Create(img.ToString(TagRenderMode.SelfClosing));
        }
    }
}

Nice and simple. The BarcodeWriter class within ZXing.NET does the hard work. We don’t need to save our QR Code to disk, and because we’re doing it inline from our HTML page via this helper, there’s no need for a separate call to get the image. Also, the caching policy that we decide to use for the page applies to the image within, simplifying things vs. two calls.

HOW TO DISPLAY A QR CODE IN WPF

Note: This code here may be wrong. I’m happy to hear your suggestion, Dear Reader, because I’m either missing something completely or there is no clear and clean way to get from a System.Drawing.Bitmap to a System.Windows.Media.imaging.BitmapImage. The little dance here with the saving to a MemoryStream, then moving into a BitmapImage (with the unintuitive but totally required setting of CacheOption as well) just sets off my Spideysense. It can’t be right, although it works.

I’ll update the post when/if a cleaner way is found.

See below for update!

First, the exact same BarcodeWriter usage from the ZXing.NET library.

var qrcode = new QRCodeWriter();
var qrValue = "your magic here";
var barcodeWriter = new BarcodeWriter
{
    Format = BarcodeFormat.QR_CODE,
    Options = new EncodingOptions
    {
        Height = 300,
        Width = 300,
        Margin = 1
    }
};
using (var bitmap = barcodeWriter.Write(qrValue))
using (var stream = new MemoryStream())
{
    bitmap.Save(stream, ImageFormat.Png);
    BitmapImage bi = new BitmapImage();
      bi.BeginInit();
        stream.Seek(0, SeekOrigin.Begin);
        bi.StreamSource = stream;
        bi.CacheOption = BitmapCacheOption.OnLoad;
      bi.EndInit();
      QRCode.Source = bi; //A WPF Image control
}

Later, writing the Bitmap to a MemoryStream for manipulation, except in this case, we’re putting the QR Code into the Source property of a WPF Image Control.

UPDATE: Thomas Levesque in the comments below suggests an extension within System.Windows.Interop (which explains me not finding it) called CreateBitmapSourceFromHBitmap. This still feels gross as it appears to requires a call to the native DeleteObject, but regardless, that’s the price you pay I guess. It looks like this:

using (var bitmap = barcodeWriter.Write(qrValue))
{
    var hbmp = bitmap.GetHbitmap();
    try
    {
        var source = Imaging.CreateBitmapSourceFromHBitmap(hbmp, IntPtr.Zero, Int32Rect.Empty, System.Windows.Media.Imaging.BitmapSizeOptions.FromEmptyOptions());
        QRCode.Source = source;
    }
    finally
    {
        DeleteObject(hbmp);
    }
}

It works well!

Ref: http://www.hanselman.com/blog/CategoryView.aspx?category=NuGetPOW

[Startup] Founder của mWork và Appota bật mí về hệ sinh thái startup làm di động (Video)

hanoi-meetup-tech-in-asia-720x540 copyHồi tháng Tư, Tech In Asia đã tổ chức Hội thảo đầu tiên tại Hà Nội. Trong buổi này, chúng tôi đã có cơ hội chào đón hơn 200 khách tham dự với phần trình bày của anh Dũng Trần, người sáng lập mWork, và anh Đỗ Tuấn Anh, người sáng lập Appota, về kinh nghiệm khởi nghiệp thành công trên di động. Hai anh cũng đưa ra lời khuyên cho các startup đang “máu lửa” với lĩnh vực này.

 

(Xin xem phiên bản tiếng Anh của bài viết tại đây)

 

Điểm nhấn hàng đầu trong buổi phỏng vấn chính là sự chân thực và vô cùng hài hước của hai vị khách mời. Họ là bạn bè lâu năm và họ đã trêu đùa nhau trong suốt buổi phỏng vấn. Đi từ chiếc xe hơi của anh Tuấn Anh sang đến bộ râu của anh Dũng, cả hai thậm chí chỉ trích nhau trong khi vẫn rất thân thiết và chia sẻ thực về những điểm mà một người đứng đầu startup cần có tại Việt Nam. Toàn bộ cuộc phỏng vấn bằng tiếng Việt nhưng chúng tôi đã biên dịch sang phụ đề tiếng Anh cho khán giả quốc tế.

(Chúng tôi cũng sắp tổ chức một buổi hội thảo khác tại TP. HCM với các khách mời tên tuổi như Esther Nguyễn đến từ POPS Việt Nam và blogger nổi tiếng Toàn Shinoda)

Bạn sẽ cảm nhận rõ hơn sự vui nhộn của buổi nói chuyện nếu bạn biết rằng những nhân vật mà trước đây hai vị khách mời đã làm việc cho họ, cũng có mặt ở đó (một số còn đặt câu hỏi vào cuối buổi), làm cho không khí buổi nói chuyện càng chân thực và gần gũi. Vì vậy, chúng tôi khuyên bạn nên xem từ đầu đến cuối các video. Nhưng đối với những ai không có thời gian, chúng tôi tóm gọn những điểm chính của buổi nói chuyện lại như bên dưới:

  • Apota đã nhận đầu tư hồi đầu năm 2012 và đến cuối năm đó đã tiêu hết tiền.
  • Dũng đã thất bại nhiều lần trước khi làm mWork. Anh thậm chí còn thất bại khi mở một công ty truyền thông, và quay lại làm việc cho FPT.
  • Dũng nói rằng thị trường game di động Việt Nam hiện đang có giá trị một tỷ đô.
  • Dũng nói rằng thương mại điện tử sẽ trở thành làn sóng lớn tiếp theo trên thiết bị di động.
  • Tuấn Anh nói rằng đích đến lớn nhất của Appota là đóng góp nhiều hơn nữa cho hệ sinh thái khởi nghiệp ở Việt Nam.
  • Dũng nói sau năm 2015, thị trường sẽ ổn định hơn và khi đó khó lòng mà các startup mới bước vào thị trường, nếu không thực sự đột phá.
  • Dũng tin rằng có ba yếu tố chính dẫn đến thành công: chọn đúng ngành nghề, chọn đúng mô hình, và tuyển dụng được một đội ngũ nhân sự tốt.
  • Tuấn Anh tin rằng chìa khoá thành công chính là biết cách kết hợp hài hòa những gì chúng ta yêu thích và những gì thị trường cần.

Bạn có thể theo dõi toàn bộ buổi phỏng vấn bên dưới:

Cuối cùng, chúng tôi muốn gửi lời cảm ơn đến những người bạn của chúng tôi là Hatch, Topica, VTC, và Aiti Education, các bạn đã giúp quảng bá sự kiện này cho hệ sinh thái khởi nghiệp đang lớn mạnh của Hà Nội. Chúng tôi cũng muốn gửi lời cảm ơn chân thành đến Techcrunch đã tài trợ cho buổi tiệc thân mật sau sự kiện.

hanoi-meetup-techcrunch-tech-in-asia-hanoi-720x540

Biên tập bởi Jeffrey Quigley

Biên dịch bởi Quyen Quyen