นับตั้งแต่วันที่ 1 พฤษภาคม 2024 Microsoft ได้ยุติการสนับสนุน Xamarin SDK ทั้งหมดอย่างเป็นทางการ รวมถึง Xamarin.Forms ด้วย ซึ่งหมายความว่าไม่มีการอัปเดตความปลอดภัย ไม่มีการแก้ไขบั๊ก และ Visual Studio เวอร์ชันใหม่ก็ไม่รองรับ Xamarin.Forms อีกต่อไป ทีมพัฒนาที่ยังคงใช้ Xamarin อยู่กำลังเผชิญกับความเสี่ยงจริง ๆ ทั้ง CI/CD pipeline ที่เริ่มล้มเหลว, ความไม่เข้ากันกับ iOS และ Android SDK เวอร์ชันล่าสุด และระบบ App Store ที่อาจปฏิเสธแอปได้ในอนาคต
พูดตรง ๆ เลยก็คือ — ถ้าคุณยังอยู่บน Xamarin ในปี 2026 นี่ถึงเวลาที่ต้องย้ายแล้วจริง ๆ ครับ
.NET MAUI (Multi-platform App UI) คือ framework ที่ Microsoft สร้างขึ้นเป็นตัวแทนโดยตรงของ Xamarin.Forms ข้อดีมีหลายอย่างชัดเจนมาก ไม่ว่าจะเป็น Single Project Structure ที่ลดความซับซ้อนของโปรเจกต์ลงไปได้เยอะ, Handler Architecture ที่เร็วกว่า Renderer เก่าอย่างมีนัยสำคัญ, การสนับสนุน NativeAOT ทำให้แอปเปิดเร็วขึ้น และยังรวม Xamarin.Essentials ไว้ใน .NET MAUI โดยตรงโดยไม่ต้องติดตั้ง package แยก (ซึ่งตอนแรกไม่รู้ว่านี่ทำให้ชีวิตง่ายขึ้นเยอะมาก)
ทำไมต้องย้ายจาก Xamarin.Forms ไป .NET MAUI ตอนนี้?
ในปี 2026 การคงอยู่บน Xamarin ไม่ใช่แค่ปัญหาหนี้ทางเทคนิค (technical debt) อีกต่อไป — มันคือความเสี่ยงด้านความปลอดภัยโดยตรง ปัญหาที่ทีมพัฒนากำลังเผชิญอยู่:
- ไม่มี Security Patch — ช่องโหว่ที่ค้นพบใหม่จะไม่ได้รับการแก้ไขใน Xamarin
- CI/CD Pipeline พัง — เครื่องมือ build สมัยใหม่หยุดรองรับ Xamarin SDK
- ปัญหากับ iOS/Android SDK ใหม่ — Apple และ Google บังคับให้ใช้ SDK เวอร์ชันล่าสุดสำหรับการ submit แอป
- Visual Studio 2026 — ไม่รองรับโปรเจกต์ Xamarin.Forms อีกต่อไป
ในทางตรงกันข้าม .NET MAUI บน .NET 10 นำเสนอประสิทธิภาพที่ดีกว่าในทุกมิติ startup time เร็วขึ้นอย่างมาก (ขอบคุณ NativeAOT), binding execution ลดลงจาก 47ns เหลือ 33ns และ memory allocation ลดลงครึ่งหนึ่งในการ binding แต่ละครั้ง — ตัวเลขพวกนี้ฟังดูเล็กน้อย แต่รับรองว่าผู้ใช้สัมผัสได้ในแอปจริง
ประเมินความพร้อมก่อนเริ่ม Migration
วิเคราะห์ความซับซ้อนของโปรเจกต์
ก่อนเริ่ม migration สิ่งสำคัญที่สุดคือการประเมินขนาดและความซับซ้อนของโปรเจกต์ปัจจุบัน เพื่อเลือกกลยุทธ์ที่เหมาะสม ตัวชี้วัดที่ดีที่สุดคือจำนวน Custom Renderer ในโปรเจกต์
- โปรเจกต์ขนาดเล็ก — ไม่เกิน 20 หน้าจอ, Custom Renderer น้อยกว่า 5 ตัว: ใช้ Upgrade Assistant ได้เลย คาดว่าใช้เวลา 1-2 สัปดาห์
- โปรเจกต์ขนาดกลาง — 30-60 หน้าจอ, Custom Renderer 10-20 ตัว: ผสมทั้ง Upgrade Assistant และ Manual Migration คาดว่า 4-8 สัปดาห์
- โปรเจกต์ขนาดใหญ่ — มากกว่า 60 หน้าจอ, Custom Renderer จำนวนมาก: แนะนำ Manual Migration แบบค่อยเป็นค่อยไป (อันนี้ต้องอดทนหน่อยนะ)
Checklist ก่อนเริ่ม
- อัปเดต Xamarin.Forms เป็นเวอร์ชัน 5.0 และใช้ .NET Standard 2.0 หรือสูงกว่า
- ติดตั้ง Visual Studio 2022 เวอร์ชัน 17.6.0 ขึ้นไป พร้อม .NET MAUI workload
- Backup โปรเจกต์ทั้งหมดและสร้าง Git branch ใหม่สำหรับ migration
- ตรวจสอบ NuGet packages ทั้งหมดว่ามีเวอร์ชันที่รองรับ .NET MAUI หรือไม่
- บันทึก Benchmark ของแอปปัจจุบัน (startup time, memory, frame rate) เพื่อเปรียบเทียบหลัง migration — ขั้นตอนนี้หลายคนข้ามแต่แล้วจะเสียดายทีหลัง
วิธีที่ 1: ใช้ .NET Upgrade Assistant (แนะนำสำหรับโปรเจกต์ขนาดกลาง)
Microsoft มีเครื่องมือ .NET Upgrade Assistant ที่ช่วยจัดการงานซ้ำซากอัตโนมัติ เช่น การแปลง project file, อัปเดต NuGet packages และเปลี่ยน namespace พื้นฐาน โดยส่วนตัวแล้วผมแนะนำให้เริ่มจากตรงนี้ก่อนเสมอ ไม่ว่าขนาดโปรเจกต์จะใหญ่หรือเล็กแค่ไหนก็ตาม
ติดตั้งและรัน Upgrade Assistant
# ติดตั้ง Upgrade Assistant ผ่าน .NET CLI
dotnet tool install -g upgrade-assistant
# รัน migration สำหรับ solution ของคุณ
upgrade-assistant upgrade YourApp.sln
หากใช้ Visual Studio ให้คลิกขวาที่โปรเจกต์ใน Solution Explorer แล้วเลือก Upgrade ซึ่งจะแสดง wizard แนะนำทีละขั้นตอน
สิ่งที่ Upgrade Assistant จัดการให้อัตโนมัติ
- แปลง project file จาก Xamarin format เป็น SDK-style พร้อมตั้งค่า
<UseMaui>true</UseMaui> - อัปเดต target framework เป็น
net10.0-androidและnet10.0-ios - ลบ NuGet packages:
Xamarin.Forms,Xamarin.Essentialsและเพิ่ม .NET MAUI Community Toolkit แทน - เปลี่ยน namespace:
Xamarin.Forms→Microsoft.Maui.Controls - เปลี่ยน XAML xmlns จาก
https://xamarin.com/schemas/2014/formsเป็นhttps://schemas.microsoft.com/dotnet/2021/maui
ข้อจำกัดสำคัญ: Upgrade Assistant ช่วยได้ประมาณ 55-70% ของงานทั้งหมด ส่วนที่เหลือต้องทำด้วยตนเอง โดยเฉพาะ Custom Renderer และ API ที่มีพฤติกรรมเปลี่ยนแปลงไป — ดังนั้นอย่าคาดหวังว่ามันจะทำทุกอย่างได้เองโดยอัตโนมัติ 100%
วิธีที่ 2: Manual Migration (สำหรับโปรเจกต์ขนาดใหญ่)
สำหรับโปรเจกต์ที่ซับซ้อน แนะนำให้สร้าง .NET MAUI project ใหม่และย้าย code แบบค่อยเป็นค่อยไป แทนการอัปเกรด in-place เพราะสามารถตรวจสอบทุกส่วนได้ละเอียดกว่า และลดความเสี่ยงที่ปัญหาหนึ่งจะลามไปกระทบส่วนอื่น
การเปลี่ยน Namespace ที่พบบ่อย
// Xamarin.Forms (เก่า)
using Xamarin.Forms;
using Xamarin.Essentials;
// .NET MAUI (ใหม่)
using Microsoft.Maui.Controls;
using Microsoft.Maui;
using Microsoft.Maui.ApplicationModel; // แทน Xamarin.Essentials
using Microsoft.Maui.Devices; // สำหรับ device info
<!-- XAML: Xamarin.Forms เก่า -->
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
<!-- XAML: .NET MAUI ใหม่ -->
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
จัดการ Breaking Changes ที่สำคัญ
Layout Control ที่เปลี่ยนไป
หนึ่งในการเปลี่ยนแปลงที่พบบ่อยในโค้ด XAML คือ Layout controls ที่ถูกยกเลิกหรือเปลี่ยนชื่อ อันนี้ทำให้หลายคนงงตอนแรก แต่จริง ๆ แล้วมันสมเหตุสมผลมากกว่าของเดิม
<!-- Xamarin.Forms: StackLayout -->
<StackLayout Orientation="Vertical">
<Label Text="หัวข้อ" />
<Label Text="เนื้อหา" />
</StackLayout>
<!-- .NET MAUI: VerticalStackLayout (ชัดเจนกว่า, เร็วกว่า) -->
<VerticalStackLayout>
<Label Text="หัวข้อ" />
<Label Text="เนื้อหา" />
</VerticalStackLayout>
<!-- RelativeLayout ถูกยกเลิก — ใช้ Grid แทน -->
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Label Grid.Row="0" Text="Header" />
<ScrollView Grid.Row="1">
<!-- เนื้อหา -->
</ScrollView>
</Grid>
API ที่เปลี่ยนพฤติกรรมในปี 2026
// MessagingCenter (deprecated — ใน .NET 10 เป็น internal แล้ว)
MessagingCenter.Send(this, "UserLoggedIn", userData);
// WeakReferenceMessenger (ทางเลือกใหม่ — เร็วกว่า 100x)
// ต้องติดตั้ง: CommunityToolkit.Mvvm
WeakReferenceMessenger.Default.Send(new UserLoggedInMessage(userData));
WeakReferenceMessenger.Default.Register<UserLoggedInMessage>(this, (r, m) => {
// จัดการ message
});
// DependencyService (เก่า — ไม่แนะนำ)
var service = DependencyService.Get<IMyService>();
// .NET Dependency Injection (ใหม่ — แนะนำ)
// ลงทะเบียนใน MauiProgram.cs:
builder.Services.AddSingleton<IMyService, MyService>();
// Inject ผ่าน Constructor Injection:
public class MainViewModel {
private readonly IMyService _myService;
public MainViewModel(IMyService myService) {
_myService = myService;
}
}
// Color API เปลี่ยนไป
var red = Color.Red; // เก่า
var red = Colors.Red; // ใหม่
var hex = Color.FromHex("#FF5733"); // เก่า
var hex = Color.FromArgb("#FF5733"); // ใหม่
// Device timer เปลี่ยนไป
Device.StartTimer(TimeSpan.FromSeconds(1), () => true); // เก่า
Dispatcher.StartTimer(TimeSpan.FromSeconds(1), () => true); // ใหม่
ส่วนที่ท้าทายที่สุด: ย้าย Custom Renderer เป็น Handler
นี่คือส่วนที่ใช้เวลามากที่สุดและส่งผลต่อ performance มากที่สุด Handler Architecture ใน .NET MAUI ไม่สร้าง parent view container เหมือน Renderer เก่า ทำให้ visual hierarchy เล็กลงและ rendering เร็วขึ้นอย่างชัดเจน ถ้าต้องเลือกส่วนเดียวที่ไม่ควรข้ามหรือใช้ shim ชั่วคราวแล้วลืมทำ — ส่วนนี้แหละ
ทางเลือกที่ 1: ใช้ Shim (เร็ว แต่ชั่วคราว)
.NET MAUI รองรับ Xamarin.Forms custom renderers บางส่วนผ่าน compatibility shim เหมาะสำหรับช่วงแรกของการ migrate
// MauiProgram.cs — ใช้ Renderer เก่าผ่าน Shim
public static class MauiProgram {
public static MauiApp CreateMauiApp() {
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>()
.ConfigureMauiHandlers((handlers) => {
#if ANDROID
handlers.AddHandler(typeof(MyCustomView),
typeof(MyApp.Platforms.Android.Renderers.MyCustomViewRenderer));
#elif IOS
handlers.AddHandler(typeof(MyCustomView),
typeof(MyApp.Platforms.iOS.Renderers.MyCustomViewRenderer));
#endif
});
return builder.Build();
}
}
ข้อควรระวัง: การใช้ shim ในระยะยาวทำให้เกิด memory leak บน OS เวอร์ชันใหม่ และไม่ได้ประโยชน์จาก Handler performance improvements ควรวางแผน rewrite เป็น Handler จริงในภายหลัง (อย่าปล่อยทิ้งไว้นานเกินไปนะ)
ทางเลือกที่ 2: เขียน Handler ใหม่ (แนะนำสำหรับระยะยาว)
ตัวอย่างต่อไปนี้แสดงการแปลง Entry Renderer ที่มี rounded corner จาก Xamarin.Forms เป็น .NET MAUI Handler อย่างสมบูรณ์
// XAMARIN.FORMS เก่า — Custom Renderer (iOS)
[assembly: ExportRenderer(typeof(RoundedEntry), typeof(RoundedEntryRenderer))]
namespace MyApp.iOS.Renderers {
public class RoundedEntryRenderer : EntryRenderer {
protected override void OnElementChanged(
ElementChangedEventArgs<Entry> e) {
base.OnElementChanged(e);
if (Control != null) {
Control.Layer.CornerRadius = 10;
Control.Layer.BorderWidth = 1;
Control.Layer.BorderColor = UIColor.SystemGray.CGColor;
}
}
}
}
// .NET MAUI ใหม่ — ขั้นที่ 1: กำหนด cross-platform control
// RoundedEntry.cs (ไฟล์หลัก shared)
public class RoundedEntry : Entry { }
// ขั้นที่ 2: กำหนด Handler (RoundedEntryHandler.cs)
public partial class RoundedEntryHandler : EntryHandler {
public static new IPropertyMapper<IEntry, RoundedEntryHandler> PropertyMapper =
new PropertyMapper<IEntry, RoundedEntryHandler>(EntryHandler.Mapper) {
[nameof(IView.Background)] = MapBackground
};
public RoundedEntryHandler() : base(PropertyMapper) { }
}
// ขั้นที่ 3a: iOS implementation
// Platforms/iOS/RoundedEntryHandler.cs
public partial class RoundedEntryHandler {
private static void MapBackground(
RoundedEntryHandler handler, IEntry entry) {
handler.PlatformView.Layer.CornerRadius = 10;
handler.PlatformView.Layer.BorderWidth = 1;
handler.PlatformView.Layer.BorderColor = UIColor.SystemGray.CGColor;
handler.PlatformView.ClipsToBounds = true;
}
}
// ขั้นที่ 3b: Android implementation
// Platforms/Android/RoundedEntryHandler.cs
public partial class RoundedEntryHandler {
private static void MapBackground(
RoundedEntryHandler handler, IEntry entry) {
var drawable = new GradientDrawable();
drawable.SetCornerRadius(30f);
drawable.SetStroke(2, Android.Graphics.Color.Gray);
handler.PlatformView.Background = drawable;
}
}
// ขั้นที่ 4: ลงทะเบียนใน MauiProgram.cs
builder.ConfigureMauiHandlers(handlers => {
handlers.AddHandler<RoundedEntry, RoundedEntryHandler>();
});
สำคัญ: Handler มี scope เป็น global หมายความว่าการปรับแต่ง Handler จะส่งผลกับทุก control ประเภทนั้นในแอปทั้งหมด หากต้องการปรับแต่งเฉพาะบาง instance ให้ subclass control แยกก่อนแล้วค่อยสร้าง Handler ใหม่
ConnectHandler และ DisconnectHandler
// จัดการ event subscription อย่างถูกต้องเพื่อป้องกัน memory leak
public partial class MyButtonHandler : ButtonHandler {
protected override void ConnectHandler(
Android.Widget.Button platformView) {
base.ConnectHandler(platformView);
platformView.Touch += OnTouch; // Subscribe
}
protected override void DisconnectHandler(
Android.Widget.Button platformView) {
platformView.Touch -= OnTouch; // Unsubscribe เสมอ
base.DisconnectHandler(platformView);
}
private void OnTouch(object? sender, View.TouchEventArgs e) {
// Handle touch event
}
}
ตั้งค่า MauiProgram.cs ที่สมบูรณ์
public static class MauiProgram {
public static MauiApp CreateMauiApp() {
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>()
.ConfigureFonts(fonts => {
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
})
.ConfigureMauiHandlers(handlers => {
handlers.AddHandler<RoundedEntry, RoundedEntryHandler>();
// เพิ่ม Handlers อื่น ๆ ที่นี่
});
// ลงทะเบียน Services (แทน DependencyService เก่า)
builder.Services.AddSingleton<IDataService, DataService>();
builder.Services.AddSingleton<INavigationService, NavigationService>();
builder.Services.AddTransient<MainViewModel>();
builder.Services.AddTransient<MainPage>();
// ลงทะเบียน Shell Routes
Routing.RegisterRoute(nameof(DetailPage), typeof(DetailPage));
return builder.Build();
}
}
การทดสอบและตรวจสอบหลัง Migration
หลังจาก migration เสร็จแล้ว ควรทดสอบบนอุปกรณ์จริงทั้ง Android และ iOS เสมอ เพราะ emulator อาจไม่แสดงปัญหาด้าน performance ที่แท้จริง (เรียนรู้จากประสบการณ์จริง — emulator ผ่านทุกอย่าง แต่พออยู่บนเครื่องจริงกลับช้า) สิ่งที่ต้องตรวจสอบ:
- Startup time — ควรเร็วขึ้นเมื่อเทียบกับ benchmark เดิม เนื่องจาก NativeAOT และ Handler architecture
- Memory usage — ใช้
dotnet-dsrouterและdotnet-gcdumpเพื่อตรวจหา memory leak - UI rendering — ตรวจสอบ custom control ทุกตัว โดยเฉพาะที่ย้ายจาก Renderer เป็น Handler
- Navigation flow — ทดสอบ deep link และ back navigation ทุก flow
- Platform permissions — ตรวจสอบ Info.plist (iOS) และ AndroidManifest.xml ว่า migrate มาครบ
เมื่อ migration เสร็จสมบูรณ์แล้ว คุณพร้อมที่จะใช้ฟีเจอร์ใหม่ของ .NET MAUI ได้เต็มที่ ไม่ว่าจะเป็น การสร้างแอป Blazor Hybrid เพื่อแชร์โค้ดระหว่างเว็บและมือถือ หรือระบบแจ้งเตือนที่ทันสมัย อย่างที่อธิบายไว้ใน คู่มือตั้งค่า Push Notifications ด้วย Firebase FCM
ปัญหาที่พบบ่อยและวิธีแก้ไข
- Build error: namespace not found — Namespace ยังเป็น Xamarin.Forms: ใช้ Find & Replace ทั้ง project หรือ Upgrade Assistant
- RelativeLayout ไม่ compile — ถูกยกเลิกใน .NET MAUI: เปลี่ยนเป็น Grid ด้วย RowDefinitions/ColumnDefinitions
- Memory leak หลัง migration — ลืม DisconnectHandler สำหรับ event subscription: เพิ่ม DisconnectHandler ใน Handler ทุกตัวที่ subscribe events
- Custom Font ไม่แสดง — Font registration เปลี่ยนไป: ลงทะเบียนผ่าน
ConfigureFontsใน MauiProgram.cs - QueryProperty ไม่ปลอดภัยกับ NativeAOT — ใช้
IQueryAttributableinterface แทน เพื่อรับ navigation parameters อย่างปลอดภัย
คำถามที่พบบ่อย
Xamarin.Forms หยุดให้บริการตั้งแต่เมื่อไหร่?
Microsoft ยุติการสนับสนุน Xamarin SDK ทั้งหมดอย่างเป็นทางการเมื่อวันที่ 1 พฤษภาคม 2024 หลังจากวันนั้น ไม่มีการอัปเดตความปลอดภัย ไม่มีการแก้ไขบั๊ก และ Visual Studio เวอร์ชันใหม่ก็ไม่รองรับ Xamarin.Forms อีกต่อไป
ต้องใช้เวลานานแค่ไหนในการย้ายแอปจาก Xamarin.Forms ไป .NET MAUI?
ขึ้นอยู่กับขนาดโปรเจกต์ โปรเจกต์ขนาดเล็ก (ไม่เกิน 20 หน้าจอ) ใช้เวลา 1-2 สัปดาห์ โปรเจกต์ขนาดกลาง (30-60 หน้าจอ, Custom Renderer 10-20 ตัว) ใช้เวลา 4-8 สัปดาห์ ส่วนโปรเจกต์ขนาดใหญ่อาจใช้เวลานานกว่านั้น โดยเฉพาะถ้ามี Custom Renderer จำนวนมาก
Custom Renderer เก่าจาก Xamarin.Forms ยังใช้ได้ใน .NET MAUI ไหม?
ใช้ได้บางส่วนผ่าน compatibility shim สำหรับ Renderer บางประเภท แต่ไม่แนะนำสำหรับระยะยาว เพราะอาจเกิด memory leak บน iOS/Android เวอร์ชันใหม่ ควรแปลงเป็น Handler ซึ่งเป็น architecture ใหม่ที่ให้ performance ดีกว่า
MessagingCenter ยังใช้ได้ใน .NET MAUI บน .NET 10 ไหม?
ไม่ — ใน .NET 10 MessagingCenter ถูกทำให้เป็น internal แล้ว ไม่สามารถเรียกใช้งานได้โดยตรงจาก code ภายนอก ทางเลือกที่แนะนำคือ WeakReferenceMessenger จาก CommunityToolkit.Mvvm ซึ่งเร็วกว่ากว่า 100 เท่าและป้องกัน memory leak โดยอัตโนมัติด้วย weak reference
ควรใช้ .NET Upgrade Assistant หรือทำ Manual Migration?
สำหรับโปรเจกต์ขนาดเล็กถึงกลาง แนะนำให้เริ่มต้นด้วย Upgrade Assistant เพราะช่วยจัดการงานซ้ำซากได้ 55-70% จากนั้นค่อยทำ Manual Migration สำหรับส่วนที่เหลือ โดยเฉพาะ Custom Renderer สำหรับโปรเจกต์ขนาดใหญ่หรือซับซ้อนมาก แนะนำให้สร้าง .NET MAUI project ใหม่และย้าย code ทีละส่วนแบบ Manual เพื่อควบคุมคุณภาพได้ดีกว่า