5 دلیل برای استفاده از یک ابزار ORM
در مورد typed dataset ها ، بله نسبتا تا حدودی و تا حد نازلی بله! شبه ORM هستند که این مشکلات را دارند:
- مشکل Synchronization بین آنها و دیتابیس مساله ساز است که در یک ORM خوب باید این مساله حل شده باشد.
- join table queries در طراح آن کار نمیکند!
- query syntax استانداردی نداشته و هنگام کار با دیتابیسهای مختلف (نوعهای مختلف) این مورد مساله ساز میشود.
- typed dataset کمتر حال و هوای یک ORM واقعی و دنیای شیء گرایی با اشیایی که وابستگی کمی به دیتابیس دارند را ارائه میدهد.
- کلا dataset اشیایی با سربار بالا در دات نت فریم ورک مطرح هستند و زمانیکه کارآیی مطرح هست سعی میشود به روشهایی دیگری کوچ شود.
همچنین از لحاظ مباحث serialization هم بسیار ضعیف و کند عمل میکنند.
- زمانیکه از typed dataset استفاده میکنید عملا مدل رابطهای دیتابیس خود را با business layer مخلوط کردهاید.
سشن چیست؟
شیء سشن، مجموعهای از اشیاء serialized مرتبط با جلسهی کاری جاری یک کاربر است. این اشیاء عموما در حافظهی محلی سرور ذخیره میشوند؛ اما امکان ذخیره سازی توزیع شدهی آنها در بانکهای اطلاعاتی نیز پیش بینی شدهاست.
عموما استفادهی از اشیاء سشن توصیه نمیشوند. از این جهت که این نوع اشیاء بسیار شبیه هستند به متغیرهای سراسری و وجود این نوع متغیرها اساسا ضعف طراحی شیءگرا به حساب میآیند. اما با توجه به ماهیت stateless بودن برنامههای وب، به این معنا که با پایان رندر یک صفحه، تمام اشیاء مرتبط با آنها نیز در سمت سرور تخریب میشوند، نیاز است برای یک سری از دادههای عمومی کاربر، راه حلی را پیدا کرد تا بتوان از اطلاعات آنها استفادهی مجدد کرد. برای مثال نگهداری رشتهی اتصالی بانک اطلاعاتی که کاربر در حین لاگین به سیستم آنرا انتخاب کردهاست (اگر برنامه به ازای هر سال از یک بانک اطلاعاتی مجزا استفاده میکند) و یا زمانیکه کاربری captcha را پر میکند و مقدار آنرا به سمت سرور ارسال میکند، نیاز است مقدار ارسالی او را با مقدار ابتدایی captcha مقایسه کرد. یک چنین اطلاعاتی نباید با پایان رندر صفحه تخریب شوند و نیاز است تا زمانیکه جلسهی کاری کاربر به پایان نرسیدهاست، در دسترس باشند. به همین جهت است که مفهومی را به نام «اشیاء سشن» طراحی کردهاند.
درکل خارج از این موارد بهتر است از سشن استفاده نکنید و در جای جای برنامهی خود ردپای آنرا باقی نگذارید و به خاطر داشته باشید:
متغیر سشن = متغیر سراسری = ضعف طراحی شیءگرا
توصیهی به استفادهی از روشهای سبک وزنتر
سشنها تنها روش به اشتراک گذاری اطلاعات نیستند. اگر میخواهید اطلاعاتی را در بین میان افزارهای برنامه در طی یک درخواست به اشتراک بگذارید، شاید سشن هم یک راه حل باشد؛ اما راه حلی سنگین وزن. راه حل بهتر برای این موارد، استفادهی از HttpContext.Items است. HttpContext.Items نیز همانند سشن، یک key/value store است؛ اما طول عمر آن محدود است به طول عمر درخواست جاری و در تمام میان افزارهای برنامه در دسترس است.
برای مثال در یک میان افزار آنرا تنظیم میکنید:
app.Use(async (context, next) => { context.Items["isVerified"] = true; await next.Invoke(); });
app.Run(async (context) => { await context.Response.WriteAsync("Verified request? " + context.Items["isVerified"]); });
فعال سازی سشنها در ASP.NET Core
ASP.NET Core یک choose-what-you-need framework است. به این معنا که تا زمانیکه قابلیتی را به صورت صریح فعال سازی نکرده باشید، در دسترس نخواهد بود. همین مساله در نهایت به کاهش مصرف منابع این نوع برنامهها و همچنین طراحی ماژولار سیستم ختم میشوند. برای مثال در نگارشهای قبلی ASP.NET (تمام نگارشها)، سشنها به صورت پیش فرض فعال هستند، مگر آنکه HTTP Module آنرا در فایل web.config حذف کنید؛ اما در اینجا برعکس است.
اگر تنها در موارد خاصی که ذکر شد، نیاز به استفادهی از متغیرهای سشن را داشتید، روش فعال سازی آن به صورت ذیل است:
الف) نصب بستهی نیوگت Microsoft.AspNetCore.Session
برای این منظور وابستگی ذیل را به فایل project.json اضافه کنید:
{ "dependencies": { //same as before "Microsoft.AspNetCore.Session": "1.0.0" } }
برای این کار به کلاس آغازین برنامه مراجعه کرده و ابتدا سرویس سشنها را فعال کنید:
public void ConfigureServices(IServiceCollection services) { services.AddSession();
public void Configure(IApplicationBuilder app) { app.UseSession();
app.UseSession(options: new SessionOptions { IdleTimeout = TimeSpan.FromMinutes(30), CookieName = ".MyApplication" });
روش استفادهی از سشنها
در مثال ذیل نحوهی ذخیره سازی اطلاعات را در شیء سشن جلسهی جاری یک کاربر، ملاحظه میکنید:
public ActionResult TestSession() { this.HttpContext.Session.Set("key-1", BitConverter.GetBytes(DateTime.Now.Ticks)); this.HttpContext.Session.SetInt32("key-2", 1); this.HttpContext.Session.SetString("key-3", "DNT"); return Content("OK!"); }
public IActionResult Index() { byte[] key1 = this.HttpContext.Session.Get("key-1"); long key1Value = BitConverter.ToInt64(key1, 0); int? key2Value = this.HttpContext.Session.GetInt32("key-2"); string key3Value = this.HttpContext.Session.GetString("key-3"); return Content("OK!"); }
حالت Set آن آرایهای از بایتها را دریافت میکند و میتوان برای حالت serialization اشیاء، مفید باشد.
دقیقا معادل همین سه متد، متدهای Get، GetInt32 و GetString برای بازیابی مقادیر سشن طراحی شدهاند و باید دقت داشت که خروجیهای اینها میتوانند نال نیز باشند. به همین جهت خروجی GetInt32 آن نال پذیر است.
توسعهی متدهای پیش فرض کار با سشنها
سه متد یاد شدهی کار با سشنها در ASP.NET Core هرچند ضروری هستند، اما کافی نیستند. برای توسعهی آنها میتوان متدهای الحاقی را تدارک دید که نمونهای از آنها را ذیل مشاهده میکنید:
using System; using Microsoft.AspNetCore.Http; using Newtonsoft.Json; namespace Core1RtmEmptyTest.StartupCustomizations { public static class SessionExts { public static void SetDateTime(this ISession collection, string key, DateTime value) { collection.Set(key, BitConverter.GetBytes(value.Ticks)); } public static DateTime? GetDateTime(this ISession collection, string key) { var data = collection.Get(key); if (data == null) { return null; } var dateInt = BitConverter.ToInt64(data, 0); return new DateTime(dateInt); } public static void SetObject(this ISession session, string key, object value) { var stringValue = JsonConvert.SerializeObject(value); session.SetString(key, stringValue); } public static T GetObject<T>(this ISession session, string key) { var stringValue = session.GetString(key); return JsonConvert.DeserializeObject<T>(stringValue); } } }
و یا جهت کار با اشیاء پیچیدهتر میتوان از کتابخانهی JSON.NET استفاده کرد. به عبارتی در این نگارش از ASP.NET، کار سریالایز و دیسریالایز اشیاء، به برنامه نویس واگذار شدهاست و اینکه در پشت صحنه از چه کتابخانهای میخواهید استفاده کنید، در اختیار خودتان است.
البته باید دقت داشت که در اینجا وابستگی JSON.NET به صورت خودکار در دسترس است. از این جهت که بسیاری از وابستگیهای ASP.NET Core مانند مورد ذیل، به JSON.NET وابستهاند و نصب آنها به معنای نصب خودکار JSON.NET نیز هست:
{ "dependencies": { //same as before "Microsoft.Extensions.Configuration.Json": "1.0.0" } }
یک مطلب تکمیلی
در اینجا نیز امکان ذخیره سازی سشنها در بانک اطلاعاتی بجای حافظهی فرار سرور درنظر گرفته شدهاست و برای این حالت، بانکهای اطلاعاتی NoSQL ویژهای به نام key/value stores مانند بانک اطلاعاتی فوق سریع Redis پیشنهاد میشود؛ هرچند امکان کار با SQL Server نیز در اینجا وجود دارد، اما برای کش سرورهای مبتنی بر key/value ها، بانک اطلاعاتی Redis، انتخاب اول است.
Managing Application State
ASP.NET Web API فریم ورکی برای ساختن APIهای وب بر روی فریم ورک دات نت است. در این مقاله با استفاده از این فریم ورک، API وبی خواهیم ساخت که لیستی از محصولات را بر میگرداند. صفحه وب کلاینت، با استفاده از jQuery نتایج را نمایش خواهد داد.
یک پروژه Web API بسازید
در ویژوال استودیو 2013 پروژه جدیدی از نوع ASP.NET Web Application بسازید و نام آن را "ProductsApp" انتخاب کنید.
در دیالوگ New ASP.NET Project قالب Empty را انتخاب کنید و در قسمت "Add folders and core references for" گزینه Web API را انتخاب نمایید.
می توانید از قالب Web API هم استفاده کنید. این قالب با استفاده از ASP.NET MVC صفحات راهنمای API را خواهد ساخت. در این مقاله از قالب Empty استفاده میکنیم تا تمرکز اصلی، روی خود فریم ورک Web API باشد. بطور کلی برای استفاده از این فریم ورک لازم نیست با ASP.NET MVC آشنایی داشته باشید.
افزودن یک مدل
یک مدل (model) آبجکتی است که داده اپلیکیشن شما را معرفی میکند. ASP.NET Web API میتواند بصورت خودکار مدل شما را به JSON, XML و برخی فرمتهای دیگر مرتب (serialize) کند، و سپس داده مرتب شده را در بدنه پیام HTTP Response بنویسد. تا وقتی که یک کلاینت بتواند فرمت مرتب سازی دادهها را بخواند، میتواند آبجکت شما را deserialize کند. اکثر کلاینتها میتوانند XML یا JSON را تفسیر کنند. بعلاوه کلاینتها میتوانند فرمت مورد نظرشان را با تنظیم Accept header در پیام HTTP Request مشخص کنند.
بگذارید تا با ساختن مدلی ساده که یک محصول (product) را معرفی میکند شروع کنیم.
کلاس جدیدی در پوشه Models ایجاد کنید.
نام کلاس را به "Product" تغییر دهید، و خواص زیر را به آن اضافه کنید.
namespace ProductsApp.Models { public class Product { public int Id { get; set; } public string Name { get; set; } public string Category { get; set; } public decimal Price { get; set; } } }
افزودن یک کنترلر
در Web API کنترلرها آبجکت هایی هستند که درخواستهای HTTP را مدیریت کرده و آنها را به اکشن متدها نگاشت میکنند. ما کنترلری خواهیم ساخت که میتواند لیستی از محصولات، یا محصولی بخصوص را بر اساس شناسه برگرداند. اگر از ASP.NET MVC استفاده کرده اید، با کنترلرها آشنا هستید. کنترلرهای Web API مشابه کنترلرهای MVC هستند، با این تفاوت که بجای ارث بری از کلاس Controller از کلاس ApiController مشتق میشوند.
کنترلر جدیدی در پوشه Controllers ایجاد کنید.
در دیالوگ Add Scaffold گزینه Web API Controller - Empty را انتخاب کرده و روی Add کلیک کنید.
در دیالوگ Add Controller نام کنترلر را به "ProductsController" تغییر دهید و روی Add کلیک کنید.
توجه کنید که ملزم به ساختن کنترلرهای خود در پوشه Controllers نیستید، و این روش صرفا قراردادی برای مرتب نگاه داشتن ساختار پروژهها است. کنترلر ساخته شده را باز کنید و کد زیر را به آن اضافه نمایید.
using ProductsApp.Models; using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Web.Http; namespace ProductsApp.Controllers { public class ProductsController : ApiController { Product[] products = new Product[] { new Product { Id = 1, Name = "Tomato Soup", Category = "Groceries", Price = 1 }, new Product { Id = 2, Name = "Yo-yo", Category = "Toys", Price = 3.75M }, new Product { Id = 3, Name = "Hammer", Category = "Hardware", Price = 16.99M } }; public IEnumerable<Product> GetAllProducts() { return products; } public IHttpActionResult GetProduct(int id) { var product = products.FirstOrDefault((p) => p.Id == id); if (product == null) { return NotFound(); } return Ok(product); } }
کنترلر ما دو متد برای دریافت محصولات تعریف میکند:
- متد GetAllProducts لیست تمام محصولات را در قالب یک <IEnumerable<Product بر میگرداند.
- متد GetProductById سعی میکند محصولی را بر اساس شناسه تعیین شده پیدا کند.
همین! حالا یک Web API ساده دارید. هر یک از متدهای این کنترلر، به یک یا چند URI پاسخ میدهند:
URI | Controller Method |
api/products/ | GetAllProducts |
api/products/id/ | GetProductById |
برای اطلاعات بیشتر درباره نحوه نگاشت درخواستهای HTTP به اکشن متدها توسط Web API به این لینک مراجعه کنید.
فراخوانی Web API با جاوا اسکریپت و jQuery
در این قسمت یک صفحه HTML خواهیم ساخت که با استفاده از AJAX متدهای Web API را فراخوانی میکند. برای ارسال درخواستهای آژاکسی و بروز رسانی صفحه بمنظور نمایش نتایج دریافتی از jQuery استفاده میکنیم.
در پنجره Solution Explorer روی نام پروژه کلیک راست کرده و گزینه Add, New Item را انتخاب کنید.
در دیالوگ Add New Item قالب HTML Page را انتخاب کنید و نام فایل را به "index.html" تغییر دهید.
حال محتوای این فایل را با لیست زیر جایگزین کنید.
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>Product App</title> </head> <body> <div> <h2>All Products</h2> <ul id="products" /> </div> <div> <h2>Search by ID</h2> <input type="text" id="prodId" size="5" /> <input type="button" value="Search" onclick="find();" /> <p id="product" /> </div> <script src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-2.0.3.min.js"></script> <script> var uri = 'api/products'; $(document).ready(function () { // Send an AJAX request $.getJSON(uri) .done(function (data) { // On success, 'data' contains a list of products. $.each(data, function (key, item) { // Add a list item for the product. $('<li>', { text: formatItem(item) }).appendTo($('#products')); }); }); }); function formatItem(item) { return item.Name + ': $' + item.Price; } function find() { var id = $('#prodId').val(); $.getJSON(uri + '/' + id) .done(function (data) { $('#product').text(formatItem(data)); }) .fail(function (jqXHR, textStatus, err) { $('#product').text('Error: ' + err); }); } </script> </body> </html>
گرفتن لیستی از محصولات
برای گرفتن لیستی از محصولات، یک درخواست HTTP GET به آدرس "api/products/" ارسال کنید.
تابع getJSON یک درخواست آژاکسی ارسال میکند. پاسخ دریافتی هم آرایه ای از آبجکتهای JSON خواهد بود. تابع done در صورت موفقیت آمیز بودن درخواست، اجرا میشود. که در این صورت ما DOM را با اطلاعات محصولات بروز رسانی میکنیم.
$(document).ready(function () { // Send an AJAX request $.getJSON(apiUrl) .done(function (data) { // On success, 'data' contains a list of products. $.each(data, function (key, item) { // Add a list item for the product. $('<li>', { text: formatItem(item) }).appendTo($('#products')); }); }); });
گرفتن محصولی مشخص
برای گرفتن یک محصول توسط شناسه (ID) آن کافی است یک درخواست HTTP GET به آدرس "api/products/id/" ارسال کنید.
function find() { var id = $('#prodId').val(); $.getJSON(apiUrl + '/' + id) .done(function (data) { $('#product').text(formatItem(data)); }) .fail(function (jqXHR, textStatus, err) { $('#product').text('Error: ' + err); }); }
اجرای اپلیکیشن
اپلیکیشن را با F5 اجرا کنید. صفحه وب باز شده باید چیزی مشابه تصویر زیر باشد.
برای گرفتن محصولی مشخص، شناسه آن را وارد کنید و روی Search کلیک کنید.
اگر شناسه نامعتبری وارد کنید، سرور یک خطای HTTP بر میگرداند.
استفاده از F12 برای مشاهده درخواستها و پاسخ ها
هنگام کار با سرویسهای HTTP، مشاهدهی درخواستهای ارسال شده و پاسخهای دریافتی بسیار مفید است. برای اینکار میتوانید از ابزار توسعه دهندگان وب استفاده کنید، که اکثر مرورگرهای مدرن، پیاده سازی خودشان را دارند. در اینترنت اکسپلورر میتوانید با F12 به این ابزار دسترسی پیدا کنید. به برگه Network بروید و روی Start Capturing کلیک کنید. حالا صفحه وب را مجددا بارگذاری (reload) کنید. در این مرحله اینترنت اکسپلورر ترافیک HTTP بین مرورگر و سرور را تسخیر میکند. میتوانید تمام ترافیک HTTP روی صفحه جاری را مشاهده کنید.
به دنبال آدرس نسبی "api/products/" بگردید و آن را انتخاب کنید. سپس روی Go to detailed view کلیک کنید تا جزئیات ترافیک را مشاهده کنید. در نمای جزئیات، میتوانید headerها و بدنه درخواستها و پاسخها را ببینید. مثلا اگر روی برگه Request headers کلیک کنید، خواهید دید که اپلیکیشن ما در Accept header دادهها را با فرمت "application/json" درخواست کرده است.
اگر روی برگه Response body کلیک کنید، میتوانید ببینید چگونه لیست محصولات با فرمت JSON سریال شده است. همانطور که گفته شده مرورگرهای دیگر هم قابلیتهای مشابهی دارند. یک ابزار مفید دیگر Fiddler است. با استفاده از این ابزار میتوانید تمام ترافیک HTTP خود را مانیتور کرده، و همچنین درخواستهای جدیدی بسازید که این امر کنترل کاملی روی HTTP headers به شما میدهد.
قدمهای بعدی
متغیرها در ES 6
اجرا شدن مثال اول با فایرفاکس به این معنا است که هنوز پیاده سازی استاندارد ES 6 در فایرفاکس، توابع قرار گرفتهی در یک block را block-scoped نمیداند (و این پیاده سازی هنوز ناقص است).
- وجود let به نوعی پیاده سازی مفهوم fail fast است. هر چه سریعتر مشکلات مشخص شوند، بهتر از این است که در آینده با گنگ بودن میدان دید متغیرها، مشکلات دیباگ برنامه بیشتر شوند.
ASP.NET MVC #18
- همچنین اگر این پوشهها مدیریت شوند، حتی اگر کل برنامه شما محافظت شده باشد، نیاز است برای صفحه لاگین حداقل سایت کامل و درست نمایش داده شود. یعنی باید محتوای استاتیک سایت بدون اعتبارسنجی هم قابل دسترسی باشد.
- درخواست به تمام فایلها رو میشه در IIS یا حتی در ASP.NET مدیریت کرد. IIS یک قسمت نگاشت برای این مسایل دارد که چه فایلی به موتور ASP.NET نگاشت شود یا خیر. در IIS7 اگر حالت integrated pipline باشد، به صورت پیش فرض در وب کانفیگ runAllManagedModulesForAllRequests وجود دارد (یعنی در این حالت تمام فایلها از موتور ASP.NET رد خواهند شد). در IIS6 است که یا باید دستی تنظیمات را تغییر داد یا کدنویسی کرد. ضمن اینکه مدیریت این مسایل توسط IIS سربار کمتری داره و مسایل اعمال کش و فشرده سازی و غیره رو میشه خارج از پروسه برنامه توسط IIS مدیریت کرد.
در این پست به شرح کلاس Rectangle جهت رسم مستطیل و Square جهت رسم مربع میپردازیم
using System.Drawing; namespace PWS.ObjectOrientedPaint.Models { /// <summary> /// Rectangle /// </summary> public class Rectangle : Shape { #region Constructors (2) /// <summary> /// Initializes a new instance of the <see cref="Rectangle" /> class. /// </summary> /// <param name="startPoint">The start point.</param> /// <param name="endPoint">The end point.</param> /// <param name="zIndex">Index of the z.</param> /// <param name="foreColor">Color of the fore.</param> /// <param name="thickness">The thickness.</param> /// <param name="isFill">if set to <c>true</c> [is fill].</param> /// <param name="backgroundColor">Color of the background.</param> public Rectangle(PointF startPoint, PointF endPoint, int zIndex, Color foreColor, byte thickness, bool isFill, Color backgroundColor) : base(startPoint, endPoint, zIndex, foreColor, thickness, isFill, backgroundColor) { ShapeType = ShapeType.Rectangle; } /// <summary> /// Initializes a new instance of the <see cref="Rectangle" /> class. /// </summary> public Rectangle() { ShapeType = ShapeType.Rectangle; } #endregion Constructors #region Methods (1) // Public Methods (1) /// <summary> /// Draws the specified g. /// </summary> /// <param name="g">The g.</param> public override void Draw(Graphics g) { if (IsFill) g.FillRectangle(BackgroundBrush, StartPoint.X, StartPoint.Y, Width, Height); g.DrawRectangle(Pen, StartPoint.X, StartPoint.Y, Width, Height); base.Draw(g); } #endregion Methods } }
کلاس بعدی کلاس Square میباشد، که از کلاس بالا (Rectangle) ارث بری داشته است، کدهای این کلاس را در زیر مشاهده میکنید.
using System; using System.Drawing; namespace PWS.ObjectOrientedPaint.Models { /// <summary> /// Square /// </summary> public class Square : Rectangle { #region Constructors (2) /// <summary> /// Initializes a new instance of the <see cref="Square" /> class. /// </summary> /// <param name="startPoint">The start point.</param> /// <param name="endPoint">The end point.</param> /// <param name="zIndex">Index of the z.</param> /// <param name="foreColor">Color of the fore.</param> /// <param name="thickness">The thickness.</param> /// <param name="isFill">if set to <c>true</c> [is fill].</param> /// <param name="backgroundColor">Color of the background.</param> public Square(PointF startPoint, PointF endPoint, int zIndex, Color foreColor, byte thickness, bool isFill, Color backgroundColor) { float x = 0, y = 0; float width = Math.Abs(endPoint.X - startPoint.X); float height = Math.Abs(endPoint.Y - startPoint.Y); if (startPoint.X <= endPoint.X && startPoint.Y <= endPoint.Y) { x = startPoint.X; y = startPoint.Y; } else if (startPoint.X >= endPoint.X && startPoint.Y >= endPoint.Y) { x = endPoint.X; y = endPoint.Y; } else if (startPoint.X >= endPoint.X && startPoint.Y <= endPoint.Y) { x = endPoint.X; y = startPoint.Y; } else if (startPoint.X <= endPoint.X && startPoint.Y >= endPoint.Y) { x = startPoint.X; y = endPoint.Y; } StartPoint = new PointF(x, y); var side = Math.Max(width, height); EndPoint = new PointF(x+side, y+side); ShapeType = ShapeType.Square; Zindex = zIndex; ForeColor = foreColor; Thickness = thickness; BackgroundColor = backgroundColor; IsFill = isFill; } /// <summary> /// Initializes a new instance of the <see cref="Square" /> class. /// </summary> public Square() { ShapeType = ShapeType.Square; } #endregion Constructors } }
پیاده سازی پروژه نقاشی (Paint) به صورت شی گرا 1#
پیاده سازی پروژه نقاشی (Paint) به صورت شی گرا 2#
پیاده سازی پروژه نقاشی (Paint) به صورت شی گرا 3#
پیاده سازی پروژه نقاشی (Paint) به صورت شی گرا 4#
public RedirectToRouteResult Ship(int orderId) { User user = _userSession.GetCurrentUser(); Order order = _repository.GetById(orderId); if (order.IsAuthorized) { ShippingStatus status = _shippingService.Ship(order); if (!string.IsNullOrEmpty(user.EmailAddress)) { Message message = _messageBuilder .BuildShippedMessage(order, user); _emailSender.Send(message); } if (status.Successful) { return RedirectToAction("Shipped", "Order", new {orderId}); } } return RedirectToAction("NotShipped", "Order", new {orderId}); }
public RedirectToRouteResult Ship(int orderId) { var status = _orderShippingService.Ship(orderId); if (status.Successful) { return RedirectToAction("Shipped", "Order", new {orderId}); } return RedirectToAction("NotShipped", "Order", new {orderId}); }