برنامه نویسی موبایل با ionic
پشتیبانی از Markdown توسط ckeditor
SignalR
Async library for .NET to help build real-time, multi-user interactive web applications.
The real-time web is a set of technologies and practices that enable users to receive information as soon as it is published by its authors, rather than requiring that they or their software check a source periodically for updates.
- تکنولوژی جدید WebSocket (^) که خوشبختانه پشتیبانی کاملی از اون در دات نت 4.5 (چهار نقطه پنج! نه چهار و نیم!) وجود داره. اما تمام مرورگرها و تمام وب سرورها از این تکنولوژی پشتیبانی نمیکنند و تنها برخی نسخههای جدید قابلیت استفاده از آخرین ورژن WebSocket رو دارند که میشه به کروم 16 به بالا و فایرفاکس 11 به بالا و اینترنت اکسپلورر 10 اشاره کرد (برای استفاده از این تکنولوژی در ویندوز نیاز به IIS 8.0 است که متاسفانه فقط در ویندوز 8.0 موجوده):
Chrome 16, Firefox 11 and Internet Explorer 10 are currently the only browsers supporting the latest specification (RFC 6455). - یه روش دیگه Server-sent Events نام داره که دادههای جدید رو به فرم رویدادهای DOM به سمت کلاینت میفرسته(^).
- روش دیگهای که موجوده به Forever Frame معروفه که در این روش یک iframe مخفی درون کد html مسئول تبادل دادههاست. این iframe مخفی بهصورت یک بلاک Chunked (^) به سمت کلاینت فرستاده میشه. این iframe که مسئول رندر دادههای جدید در سمت کلاینت هست ارتباط خودش رو با سرور تا ابد! (برای همین بهش forever میگن) حفظ میکنه. هر وقت رویدادی سمت سرور رخ میده با استفاده از این روش دادهها بهصورت تگهای script به این فریم مخفی فرستاده میشوند و چون مرورگرها محتوای html رو به صورت افزایشی (incrementally) رندر میکنن بنابراین این اسکریپتها بهترتیب زمان دریافت اجرا میشوند. (البته ظاهرا عبارت forever frame در صنعت عکاسی! معروفتره بنابراین در جستجو در زمینه این روش ممکنه کمی مشکل داشته باشین) (^).
- روش آخر که در کتابخونه SignalR ازش استفاده میشه long-polling نام داره. در روش polling معمولی پس از ارسال درخواست توسط کلاینت، سرور بلافاصله نتیجه حاصله رو به سمت کلاینت میفرسته و ارتباط قطع میشه. بنابراین برای دادههای جدید درخواست جدیدی باید به سمت سرور فرستاده بشه که تکرار این روش باعث افزایش شدید بار بر روی سرور و کاهش کارآمدی اون میشه. اما در روش long-polling پس از برقراری ارتباط کلاینت با سرور این ارتباط تا مدت زمان معینی (که توسط یه مقدار تایم اوت مشخص میشه و مقدار پیشفرضش 2 دقیقه است) برقرار میمونه. بنابراین کلاینت میتونه بدون ایجاد مشکلی در کارایی، دادههای جدید رو از سرور دریافت کنه. به این روش در برنامهنویسی وب اصطلاحا برنامهنویسی کامت (Comet Programming) میگن (^ ^).
- کلاس سطح پایین PersistentConnection
- کلاس سطح بالای Hub
PM> Install-Package SignalR.Sample
PM> Install-Package SignalR
Microsoft.Web.Infrastructure Newtonsoft.Json SignalR SignalR.Hosting.AspNet SignalR.Hosting.Common
jquery-1.6.4.js jquery.signalR-0.5.1.js
using SignalR.Hubs; namespace SimpleChatWithSignalR { public class SimpleChat : Hub { public void SendMessage(string message) { Clients.reciveMessage(message); } } }
<input type="text" id="msg" /> <input type="button" value="Send" id="send" /><br /> <textarea id='messages' readonly="true" style="height: 200px; width: 200px;"></textarea> <script src="Scripts/jquery-1.6.4.min.js" type="text/javascript"></script> <script src="Scripts/jquery.signalR-0.5.1.min.js" type="text/javascript"></script> <script src="signalr/hubs" type="text/javascript"></script> <script type="text/javascript"> var chat = $.connection.simpleChat; chat.reciveMessage = function (msg) { $('#messages').val($('#messages').val() + "-" + msg + "\r\n"); }; $.connection.hub.start(); $('#send').click(function () { chat.sendMessage($('#msg').val()); }); </script>
<script src="signalr/hubs" type="text/javascript"></script>
به روز رسانی
در دورهای به نام SignalR در سایت، به روز شدهای این مباحث را میتوانید مطالعه کنید.
تنظیم Ember data برای کار با سرور
Ember data به صورت پیش فرض و در پشت صحنه با استفاده از Ajax برای کار با یک REST Web Service طراحی شدهاست و کلیه تبادلات آن نیز با فرمت JSON انجام میشود. بنابراین تمام کدهای سمت کاربر قسمت قبل نیز در این حالت کار خواهند کرد. تنها کاری که باید انجام شود، حذف تنظیمات ابتدایی آن برای کار با HTML 5 local storage است.
برای این منظور ابتدا فایل index.html را گشوده و سپس مدخل localstorage_adapter.js را از آن حذف کنید:
<!--<script src="Scripts/Libs/localstorage_adapter.js" type="text/javascript"></script>-->
<!--<script src="Scripts/App/store.js" type="text/javascript"></script>-->
اکنون برنامه را اجرا کنید، چنین پیام خطایی را مشاهده خواهید کرد:
همانطور که عنوان شد، ember data به صورت پیش فرض با سرور کار میکند و در اینجا به صورت خودکار، یک درخواست Get را به آدرس http://localhost:25918/posts جهت دریافت آخرین مطالب ثبت شده، ارسال کردهاست و چون هنوز وب سرویسی در برنامه تعریف نشده، با خطای 404 و یا یافت نشد، مواجه شدهاست.
این درخواست نیز بر اساس تعاریف موجود در فایل Scripts\Routes\posts.js، به سرور ارسال شدهاست:
Blogger.PostsRoute = Ember.Route.extend({ model: function () { return this.store.find('post'); } });
تغییر تنظیمات پیش فرض آغازین Ember data
آدرس درخواستی http://localhost:25918/posts به این معنا است که کلیه درخواستها، به همان آدرس و پورت ریشهی اصلی سایت ارسال میشوند. اما اگر یک ASP.NET Web API Controller را تعریف کنیم، نیاز است این درخواستها، برای مثال به آدرس api/posts ارسال شوند؛ بجای /posts.
برای این منظور پوشهی جدید Scripts\Adapters را ایجاد کرده و فایل web_api_adapter.js را با این محتوا به آن اضافه کنید:
DS.RESTAdapter.reopen({ namespace: 'api' });
<script src="Scripts/Adapters/web_api_adapter.js" type="text/javascript"></script>
تغییر تنظیمات پیش فرض ASP.NET Web API
در سمت سرور، بنابر اصول نامگذاری خواص، نامها با حروف بزرگ شروع میشوند:
namespace EmberJS03.Models { public class Post { public int Id { set; get; } public string Title { set; get; } public string Body { set; get; } } }
using System; using System.Web.Http; using System.Web.Routing; using Newtonsoft.Json.Serialization; namespace EmberJS03 { public class Global : System.Web.HttpApplication { protected void Application_Start(object sender, EventArgs e) { RouteTable.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{controller}/{id}", defaults: new { id = RouteParameter.Optional } ); var settings = GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings; settings.ContractResolver = new CamelCasePropertyNamesContractResolver(); } } }
نحوهی صحیح بازگشت اطلاعات از یک ASP.NET Web API جهت استفاده در Ember data
با تنظیمات فوق، اگر کنترلر جدیدی را به صورت ذیل جهت بازگشت لیست مطالب تهیه کنیم:
namespace EmberJS03.Controllers { public class PostsController : ApiController { public IEnumerable<Post> Get() { return DataSource.PostsList; } } }
WARNING: Encountered "0" in payload, but no model was found for model name "0" (resolved model name using DS.RESTSerializer.typeForRoot("0"))
http://jsonapi.codeplex.com
https://github.com/xqiu/MVCSPAWithEmberjs
https://github.com/rmichela/EmberDataAdapter
https://github.com/MilkyWayJoe/Ember-WebAPI-Adapter
http://blog.yodersolutions.com/using-ember-data-with-asp-net-web-api
http://emadibrahim.com/2014/04/09/emberjs-and-asp-net-web-api-and-json-serialization
و خلاصهی آنها به این صورت است:
خروجی JSON تولیدی توسط ASP.NET Web API چنین شکلی را دارد:
[ { Id: 1, Title: 'First Post' }, { Id: 2, Title: 'Second Post' } ]
{ posts: [{ id: 1, title: 'First Post' }, { id: 2, title: 'Second Post' }] }
using System.Web.Http; using EmberJS03.Models; namespace EmberJS03.Controllers { public class PostsController : ApiController { public object Get() { return new { posts = DataSource.PostsList }; } } }
اکنون اگر برنامه را اجرا کنید، در صفحهی اول آن، لیست عناوین مطالب را مشاهده خواهید کرد.
تاثیر قرارداد JSON API در حین ارسال اطلاعات به سرور توسط Ember data
در تکمیل کنترلرهای Web API مورد نیاز (کنترلرهای مطالب و نظرات)، نیاز به متدهای Post، Update و Delete هم خواهد بود. دقیقا فرامین ارسالی توسط Ember data توسط همین HTTP Verbs به سمت سرور ارسال میشوند. در این حالت اگر متد Post کنترلر نظرات را به این شکل طراحی کنیم:
public HttpResponseMessage Post(Comment comment)
{"comment":{"text":"data...","post":"3"}}
برای پردازش آن، یا باید از راه حلهای ثالث مطرح شده در ابتدای بحث استفاده کنید و یا میتوان مطابق کدهای ذیل، کل اطلاعات JSON ارسالی را توسط کتابخانهی JSON.NET نیز پردازش کرد:
namespace EmberJS03.Controllers { public class CommentsController : ApiController { public HttpResponseMessage Post(HttpRequestMessage requestMessage) { var jsonContent = requestMessage.Content.ReadAsStringAsync().Result; // {"comment":{"text":"data...","post":"3"}} var jObj = JObject.Parse(jsonContent); var comment = jObj.SelectToken("comment", false).ToObject<Comment>(); var id = 1; var lastItem = DataSource.CommentsList.LastOrDefault(); if (lastItem != null) { id = lastItem.Id + 1; } comment.Id = id; DataSource.CommentsList.Add(comment); // ارسال آی دی با فرمت خاص مهم است return Request.CreateResponse(HttpStatusCode.Created, new { comment = comment }); } } }
همچنین فرمت return نهایی هم مهم است. در این حالت خروجی ارسالی به سمت کاربر، باید مجددا با فرمت JSON API باشد؛ یعنی باید comment اصلاح شده را به همراه ریشهی comment ارسال کرد. در اینجا نیز anonymous object تهیه شده، چنین کاری را انجام میدهد.
Lazy loading در Ember data
تا اینجا اگر برنامه را اجرا کنید، لیست مطالب صفحهی اول را مشاهده خواهید کرد، اما لیست نظرات آنها را خیر؛ از این جهت که ضرورتی نداشت تا در بار اول ارسال لیست مطالب به سمت کاربر، تمام نظرات متناظر با آنها را هم ارسال کرد. بهتر است زمانیکه کاربر یک مطلب خاص را مشاهده میکند، نظرات خاص آنرا به سمت کاربر ارسال کنیم.
در تعاریف سمت کاربر Ember data، پارامتر دوم رابطهی hasMany که با async:true مشخص شدهاست، دقیقا معنای lazy loading را دارد.
Blogger.Post = DS.Model.extend({ title: DS.attr(), body: DS.attr(), comments: DS.hasMany('comment', { async: true } /* lazy loading */) });
الف) Idهای نظرات هر مطلب را به صورت یک آرایه، در بار اول ارسال لیست نظرات به سمت کاربر، تهیه و ارسال کنیم:
namespace EmberJS03.Models { public class Post { public int Id { set; get; } public string Title { set; get; } public string Body { set; get; } // lazy loading via an array of IDs public int[] Comments { set; get; } } }
ب) این روش به علت تعداد رفت و برگشت بیش از حد به سرور، کارآیی آنچنانی ندارد. بهتر است جهت مشاهدهی جزئیات یک مطلب، تنها یکبار درخواست Get کلیه نظرات آن صادر شود.
برای اینکار باید مدل برنامه را به شکل زیر تغییر دهیم:
namespace EmberJS03.Models { public class Post { public int Id { set; get; } public string Title { set; get; } public string Body { set; get; } // load related models via URLs instead of an array of IDs // ref. https://github.com/emberjs/data/pull/1371 public object Links { set; get; } public Post() { Links = new { comments = "comments" }; // api/posts/id/comments } } }
namespace EmberJS03 { public class Global : System.Web.HttpApplication { protected void Application_Start(object sender, EventArgs e) { RouteTable.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{controller}/{id}/{name}", defaults: new { id = RouteParameter.Optional, name = RouteParameter.Optional } ); var settings = GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings; settings.ContractResolver = new CamelCasePropertyNamesContractResolver(); } } }
در این حالت در طی یک درخواست میتوان کلیه نظرات را به سمت کاربر ارسال کرد. در اینجا نیز ذکر ریشهی comments همانند ریشه posts، الزامی است:
namespace EmberJS03.Controllers { public class PostsController : ApiController { //GET api/posts/id public object Get(int id) { return new { posts = DataSource.PostsList.FirstOrDefault(post => post.Id == id), comments = DataSource.CommentsList.Where(comment => comment.Post == id).ToList() }; } } }
پردازشهای async و متد transitionToRoute در Ember.js
اگر متد حذف مطالب را نیز به کنترلر Posts اضافه کنیم:
namespace EmberJS03.Controllers { public class PostsController : ApiController { public HttpResponseMessage Delete(int id) { var item = DataSource.PostsList.FirstOrDefault(x => x.Id == id); if (item == null) return Request.CreateResponse(HttpStatusCode.NotFound); DataSource.PostsList.Remove(item); //حذف کامنتهای مرتبط var relatedComments = DataSource.CommentsList.Where(comment => comment.Post == id).ToList(); relatedComments.ForEach(comment => DataSource.CommentsList.Remove(comment)); return Request.CreateResponse(HttpStatusCode.OK, new { post = item }); } } }
Attempted to handle event `pushedData` on while in state root.deleted.inFlight.
Blogger.PostController = Ember.ObjectController.extend({ isEditing: false, actions: { edit: function () { this.set('isEditing', true); }, save: function () { var post = this.get('model'); post.save(); this.set('isEditing', false); }, delete: function () { if (confirm('Do you want to delete this post?')) { var thisController = this; var post = this.get('model'); post.destroyRecord().then(function () { thisController.transitionToRoute('posts'); }); } } } });
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید:
EmberJS03_05.zip
ساخت Rest api توسط فریم ورک bottle
REST APIs have become a common way to establish an interface between web back-ends and front-ends, and between different web services. The simplicity of this kind of interface, and the ubiquitous support of the HTTP and HTTPS protocols across different networks and frameworks, makes it an easy choice when considering interoperability issues.
We are thrilled to announce the highly anticipated .NET Conf 2024, a free, three-day virtual developer event celebrating the release of .NET 9. Co-organized by the .NET community and Microsoft, this annual tradition continues to grow, and we’re more excited than ever to bring you the latest innovations in .NET. Mark your calendars for November 12th to 14th, 2024, and prepare to be inspired by a wealth of knowledge, creativity, and community engagement.
آپدیت 2 ویژوال استودیو 2015
بعد از نصب به روز رسانی دوم، اگر به اینترنت متصل نباشید، این خطاها را دریافت خواهید کرد:
Visual Studio Update 2 Extensibility Item Templates with Assembly References in Nuget Packages : This product did not download successfully: HTTP status 502: The server, while acting as a gateway or proxy to fulfill the request, received an invalid response from the upstream server it accessed. Visual Studio 2015 Software Development Kit Update 2 : This product did not download successfully: HTTP status 502: The server, while acting as a gateway or proxy to fulfill the request, received an invalid response from the upstream server it accessed. Developer Analytics Tools v5.2.0 : This product did not download successfully: HTTP status 502: The server, while acting as a gateway or proxy to fulfill the request, received an invalid response from the upstream server it accessed. JavaScript Language Service for Visual Studio : This product did not download successfully: HTTP status 502: The server, while acting as a gateway or proxy to fulfill the request, received an invalid response from the upstream server it accessed. JavaScript Project System for Visual Studio : This product did not download successfully: HTTP status 502: The server, while acting as a gateway or proxy to fulfill the request, received an invalid response from the upstream server it accessed.
لینکهای مستقیم دریافت این بستهها
Visual Studio Update 2 Extensibility Item Templates with Assembly References in Nuget Packages
Visual Studio 2015 Software Development Kit Update 2
Developer Analytics Tools v5.2.0
JavaScript Language Service for Visual Studio
JavaScript Project System for Visual Studio
روش یافتن این لینکها هم به صورت زیر است:
الف) فایل XML مربوط به فید به روز رسانی دوم را دریافت کنید:
http://go.microsoft.com/fwlink/?LinkID=646969
ب) سپس عنوان مواردی را که ذکر شده از اینترنت دریافت نشدهاند، در این فایل جستجو کنید. لینکی که در قسمت id هر کدام ذکر شده، دقیقا لینک دانلود آفلاین آن است.
در نگارشهای اخیر کتابخانه jQuery (از نگارش 1.3 به بعد) متدی به نام live به آن اضافه شده است که کاربرد آنرا در ادامه مرور خواهیم کرد.
ابتدا مثال زیر را در نظر بگیرید:
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="TestLive.aspx.cs" Inherits="TestJQueryAjax.TestLive" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>
<script src="js/jquery.js" type="text/javascript"></script>
<script type="text/javascript">
$(document).ready(function() {
$('a.mylink').click(function(e) {
var $a = $(this);
alert($a.attr('id'));
});
$('a#lnkLoad').click(function(e) {
$('div#dynContent').load('live.ashx');
});
});
</script>
</head>
<body>
<form id="form1" runat="server">
<a href='#' id='lnk1' class='mylink'>link1</a>
<br />
<a href='#' id='lnkLoad'>load .ashx</a>
<div id='dynContent'>
</div>
</form>
</body>
</html>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Services;
namespace TestJQueryAjax
{
/// <summary>
/// Summary description for $codebehindclassname$
/// </summary>
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class live : IHttpHandler
{
public void ProcessRequest(HttpContext context)
{
context.Response.ContentType = "text/plain";
context.Response.Write("<a href='#' id='lnk2' class='mylink'>link2</a>");
}
public bool IsReusable
{
get
{
return false;
}
}
}
}
همچنین اگر بر روی لینکی با id مساوی lnkLoad کلیک شود، محتوایی پویا از یک generic handler به نام live.ashx دریافت شده و به div ایی با id مساوی dynContent اضافه میگردد.
این محتوای دریافتی از generic handler ما نیز کلاسی مساوی mylink دارد، اما اینبار هر چقدر بر روی لینک اضافه شده به صفحه کلیک کنیم کار نمیکند. چرا؟ چون در هنگام فراخوانی document.ready ، این لینک وجود نداشته و روال رخدادگردانی به آن bind نشده است.
به صورت خلاصه میخواهیم روال کلیک بر روی لینکهایی با کلاس mylink همیشه کار کند. (چه در مورد عناصری در صفحه که از قبل وجود داشتهاند و چه عناصری که توسط عملیاتی Ajax ایی بعدا اضافه خواهند شد)
این مشکل با معرفی متد live حل شده است. برای این منظور تنها کافی است کد ما به صورت زیر تغییر کند:
<script type="text/javascript">
$(document).ready(function() {
$('a.mylink').live("click", function() {
var $a = $(this);
alert($a.attr('id'));
});
.
.
.
اکنون jQuery کلیه لینکهایی با کلاس مساوی mylink را که از این پس اضافه خواهند شد، به صورت live و زنده تحت نظر قرار میدهد و عکس العمل نشان خواهد داد.