Attribute Routing در ASP.NET MVC 5
من از تکنولوژی Web API در پروژههای ASP.Net Web Forms استفاده میکنم. بزرگترین مشکل من بحث آدرس دهی این WebAPIها هست. چون تعداد زیادی تابع توی Controller مینویسم و باید نحوه آدرس دهی من طوری باشه که همه رو Support کنه. ظاهراً این روش Attribute Routing راه حل مشکل من هست. میخواستم بدونم آیا راهی هست که از این تکنولوژی توی ASP.Net WebForms استفاده کنم؟
ممنون میشم جواب بدهید.
با تشکر
سرویس ما متدهای زیر را در دسترس قرار میدهد.
Relative URl | HTTP method | Action |
api/products/ | GET | گرفتن لیست تمام محصولات |
api/products/id/ | GET | گرفتن یک محصول بر اساس شناسه |
api/products?category=category/ | GET | گرفتن یک محصول بر اساس طبقه بندی |
api/products/ | POST | ایجاد یک محصول جدید |
api/products/id/ | PUT | بروز رسانی یک محصول |
api/products/id/ | DELETE | حذف یک محصول |
همانطور که مشاهده میکنید برخی از آدرس ها، شامل شناسه محصول هم میشوند. بعنوان مثال برای گرفتن محصولی با شناسه 28، کلاینت یک درخواست GET را به آدرس زیر ارسال میکند:
http://hostname/api/products/28
منابع
سرویس ما آدرس هایی برای دستیابی به دو نوع منبع (resource) را تعریف میکند:
URI | Resource |
api/products/ | لیست تمام محصولات |
api/products/id/ | یک محصول مشخص |
متد ها
چهار متد اصلی HTTP یعنی همان GET, PUT, POST, DELETE میتوانند بصورت زیر به عملیات CRUD نگاشت شوند:
- متد GET یک منبع (resource) را از آدرس تعریف شده دریافت میکند. متدهای GET هیچگونه تاثیری روی سرور نباید داشته باشند. مثلا حذف رکوردها با متد اکیدا اشتباه است.
- متد PUT یک منبع را در آدرس تعریف شده بروز رسانی میکند. این متد برای ساختن منابع جدید هم میتواند استفاده شود، البته در صورتی که سرور به کلاینتها اجازه مشخص کردن آدرسهای جدید را بدهد. در مثال جاری پشتیبانی از ایجاد منابع توسط متد PUT را بررسی نخواهیم کرد.
- متد POST منبع جدیدی میسازد. سرور آدرس آبجکت جدید را تعیین میکند و آن را بعنوان بخشی از پیام Response بر میگرداند.
- متد DELETE منبعی را در آدرس تعریف شده حذف میکند.
نکته: متد PUT موجودیت محصول (product entity) را کاملا جایگزین میکند. به بیان دیگر، از کلاینت انتظار میرود که آبجکت کامل محصول را برای بروز رسانی ارسال کند. اگر میخواهید از بروز رسانیهای جزئی/پاره ای (partial) پشتیبانی کنید متد PATCH توصیه میشود. مثال جاری متد PATCH را پیاده سازی نمیکند.
یک پروژه Web API جدید بسازید
ویژوال استودیو را باز کنید و پروژه جدیدی از نوع ASP.NET MVC Web Application بسازید. نام پروژه را به "ProductStore" تغییر دهید و OK کنید.
در دیالوگ New ASP.NET Project قالب Web API را انتخاب کرده و تایید کنید.
افزودن یک مدل
یک مدل، آبجکتی است که داده اپلیکیشن شما را نمایندگی میکند. در ASP.NET Web API میتوانید از آبجکتهای Strongly-typed بعنوان مدل هایتان استفاده کنید که بصورت خودکار برای کلاینت به فرمتهای JSON, XML مرتب (Serialize) میشوند. در مثال جاری، دادههای ما محصولات هستند. پس کلاس جدیدی بنام Product میسازیم.
در پوشه Models کلاس جدیدی با نام Product بسازید.
حال خواص زیر را به این کلاس اضافه کنید.
namespace ProductStore.Models { public class Product { public int Id { get; set; } public string Name { get; set; } public string Category { get; set; } public decimal Price { get; set; } } }
افزودن یک مخزن
ما نیاز به ذخیره کردن کلکسیونی از محصولات داریم، و بهتر است این کلکسیون از پیاده سازی سرویس تفکیک شود. در این صورت بدون نیاز به بازنویسی کلاس سرویس میتوانیم منبع دادهها را تغییر دهیم. این نوع طراحی با نام الگوی مخزن یا Repository Pattern شناخته میشود. برای شروع نیاز به یک قرارداد جنریک برای مخزنها داریم.
روی پوشه Models کلیک راست کنید و گزینه Add, New Item را انتخاب نمایید.
نوع آیتم جدید را Interface انتخاب کنید و نام آن را به IProductRepository تغییر دهید.
حال کد زیر را به این اینترفیس اضافه کنید.
namespace ProductStore.Models { public interface IProductRepository { IEnumerable<Product> GetAll(); Product Get(int id); Product Add(Product item); void Remove(int id); bool Update(Product item); } }
namespace ProductStore.Models { public class ProductRepository : IProductRepository { private List<Product> products = new List<Product>(); private int _nextId = 1; public ProductRepository() { Add(new Product { Name = "Tomato soup", Category = "Groceries", Price = 1.39M }); Add(new Product { Name = "Yo-yo", Category = "Toys", Price = 3.75M }); Add(new Product { Name = "Hammer", Category = "Hardware", Price = 16.99M }); } public IEnumerable<Product> GetAll() { return products; } public Product Get(int id) { return products.Find(p => p.Id == id); } public Product Add(Product item) { if (item == null) { throw new ArgumentNullException("item"); } item.Id = _nextId++; products.Add(item); return item; } public void Remove(int id) { products.RemoveAll(p => p.Id == id); } public bool Update(Product item) { if (item == null) { throw new ArgumentNullException("item"); } int index = products.FindIndex(p => p.Id == item.Id); if (index == -1) { return false; } products.RemoveAt(index); products.Add(item); return true; } } }
مخزن ما لیست محصولات را در حافظه محلی نگهداری میکند. برای مثال جاری این طراحی کافی است، اما در یک اپلیکیشن واقعی دادههای شما در یک دیتابیس یا منبع داده ابری ذخیره خواهند شد. همچنین استفاده از الگوی مخزن، تغییر منبع دادهها در آینده را راحتتر میکند.
افزودن یک کنترلر Web API
اگر قبلا با ASP.NET MVC کار کرده باشید، با مفهوم کنترلرها آشنایی دارید. در ASP.NET Web API کنترلرها کلاس هایی هستند که درخواستهای HTTP دریافتی از کلاینت را به اکشن متدها نگاشت میکنند. ویژوال استودیو هنگام ساختن پروژه شما دو کنترلر به آن اضافه کرده است. برای مشاهد آنها پوشه Controllers را باز کنید.
- HomeController یک کنترلر مرسوم در ASP.NET MVC است. این کنترلر مسئول بکار گرفتن صفحات وب است و مستقیما ربطی به Web API ما ندارد.
- ValuesController یک کنترلر نمونه WebAPI است.
کنترلر ValuesController را حذف کنید، نیازی به این آیتم نخواهیم داشت. حال برای اضافه کردن کنترلری جدید مراحل زیر را دنبال کنید.
در پنجره Solution Explorer روی پوشه Controllers کلیک راست کرده و گزینه Add, Controller را انتخاب کنید.
در دیالوگ Add Controller نام کنترلر را به ProductsController تغییر داده و در قسمت Scaffolding Options گزینه Empty API Controller را انتخاب کنید.
حال فایل کنترلر جدید را باز کنید و عبارت زیر را به بالای آن اضافه نمایید.
using ProductStore.Models;
public class ProductsController : ApiController { static readonly IProductRepository repository = new ProductRepository(); }
فراخوانی ()new ProductRepository طراحی جالبی نیست، چرا که کنترلر را به پیاده سازی بخصوصی از این اینترفیس گره میزند. بهتر است از تزریق وابستگی (Dependency Injection) استفاده کنید. برای اطلاعات بیشتر درباره تکنیک DI در Web API به این لینک مراجعه کنید.
گرفتن منابع
ProductStore API اکشنهای متعددی در قالب متدهای HTTP GET در دسترس قرار میدهد. هر اکشن به متدی در کلاس ProductsController مرتبط است.
Relative URl | HTTP Method | Action |
api/products/ | GET | دریافت لیست تمام محصولات |
api/products/id/ | GET | دریافت محصولی مشخص بر اساس شناسه |
api/products?category=category/ | GET | دریافت محصولات بر اساس طبقه بندی |
برای دریافت لیست تمام محصولات متد زیر را به کلاس ProductsController اضافه کنید.
public class ProductsController : ApiController { public IEnumerable<Product> GetAllProducts() { return repository.GetAll(); } // .... }
برای دریافت محصولی مشخص بر اساس شناسه آن متد زیر را اضافه کنید.
public Product GetProduct(int id) { Product item = repository.Get(id); if (item == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } return item; }
نام این متد هم با "Get" شروع میشود اما پارامتری با نام id دارد. این پارامتر به قسمت id مسیر درخواست شده (request URl) نگاشت میشود. تبدیل پارامتر به نوع داده مناسب (در اینجا int) هم بصورت خودکار توسط فریم ورک ASP.NET Web API انجام میشود.
متد GetProduct در صورت نامعتبر بودن پارامتر id استثنایی از نوع HttpResponseException تولید میکند. این استثنا بصورت خودکار توسط فریم ورک Web API به خطای 404 (Not Found) ترجمه میشود.
در آخر متدی برای دریافت محصولات بر اساس طبقه بندی اضافه کنید.
public IEnumerable<Product> GetProductsByCategory(string category) { return repository.GetAll().Where( p => string.Equals(p.Category, category, StringComparison.OrdinalIgnoreCase)); }
اگر آدرس درخواستی پارامترهای query string داشته باشد، Web API سعی میکند پارامترها را با پارامترهای متد کنترلر تطبیق دهد. بنابراین درخواستی به آدرس "api/products?category=category" به این متد نگاشت میشود.
ایجاد منبع جدید
قدم بعدی افزودن متدی به ProductsController برای ایجاد یک محصول جدید است. لیست زیر پیاده سازی ساده ای از این متد را نشان میدهد.
// Not the final implementation! public Product PostProduct(Product item) { item = repository.Add(item); return item; }
- نام این متد با "Post" شروع میشود. برای ساختن محصولی جدید کلاینت یک درخواست HTTP POST ارسال میکند.
- این متد پارامتری از نوع Product میپذیرد. در Web API پارامترهای پیچیده (complex types) بصورت خودکار با deserialize کردن بدنه درخواست بدست میآیند. بنابراین در اینجا از کلاینت انتظار داریم که آبجکتی از نوع Product را با فرمت XML یا JSON ارسال کند.
پیاده سازی فعلی این متد کار میکند، اما هنوز کامل نیست. در حالت ایده آل ما میخواهیم پیام HTTP Response موارد زیر را هم در بر گیرد:
- Response code: بصورت پیش فرض فریم ورک Web API کد وضعیت را به 200 (OK) تنظیم میکند. اما طبق پروتکل HTTP/1.1 هنگامی که یک درخواست POST منجر به ساخته شدن منبعی جدید میشود، سرور باید با کد وضعیت 201 (Created) پاسخ دهد.
- Location: هنگامی که سرور منبع جدیدی میسازد، باید آدرس منبع جدید را در قسمت Location header پاسخ درج کند.
ASP.NET Web API دستکاری پیام HTTP response را آسان میکند. لیست زیر پیاده سازی بهتری از این متد را نشان میدهد.
public HttpResponseMessage PostProduct(Product item) { item = repository.Add(item); var response = Request.CreateResponse<Product>(HttpStatusCode.Created, item); string uri = Url.Link("DefaultApi", new { id = item.Id }); response.Headers.Location = new Uri(uri); return response; }
متد CreateResponse آبجکتی از نوع HttpResponseMessage میسازد و بصورت خودکار آبجکت Product را مرتب (serialize) کرده و در بدنه پاسخ مینویسد. نکته دیگر آنکه مثال جاری، مدل را اعتبارسنجی نمیکند. برای اطلاعات بیشتر درباره اعتبارسنجی مدلها در Web API به این لینک مراجعه کنید.
بروز رسانی یک منبع
بروز رسانی یک محصول با PUT ساده است.
public void PutProduct(int id, Product product) { product.Id = id; if (!repository.Update(product)) { throw new HttpResponseException(HttpStatusCode.NotFound); } }
حذف یک منبع
برای حذف یک محصول متد زیر را به کلاس ProductsController اضافه کنید.
public void DeleteProduct(int id) { Product item = repository.Get(id); if (item == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } repository.Remove(id); }
در مثال جاری متد DeleteProduct نوع void را بر میگرداند، که فریم ورک Web API آن را بصورت خودکار به کد وضعیت 204 (No Content) ترجمه میکند.