יום שלישי, 23 בדצמבר 2014

חיבור האתר לפייסבוק

לאחר החיבור האתר לפייס נוכל לקבל פרטים של המשתמש: ID בפייס, תמונה, כינוי, שם פרטי. 
רוצים לנסות?
כנסו לAPI של פייסבוק ותקבלו את התשובות בJSON
תוכלו לרשום שם לדו' את מספר משתמש: 851080087
נקבל את כל הפרטים שהמשתמש הזה אישר שיהיו ציבוריים. לדו':
{
  "id": "851080087",
  "first_name": "Swapnil",
  "last_name": "Patil",
  "link": "https://www.facebook.com/app_scoped_user_id/10154995541255088/",
  "name": "Swapnil Patil",
  "updated_time": "2014-08-24T03:53:08+0000"
}

בשביל לבצע הטמעה של התמונה של המשתמש הזה, נוכל בקלות לקבל את התמונה שלו, על ידי המחרוזת הבאה:
http://graph.facebook.com/851080087/picture?type=small
נוכל לבקש את התמונה, בגדלים שונים: small, normal, large, square.

החיבור לאתר לפייסבוק יכול להתבצע על ידי שתי אפשרויות:

ראשית, בשביל לבצע חיבור לאחר שנבצע הרשמה באתר של פייסבוק של האפליקציה שלנו (פשוט ללכת אחרי ההוראות בכתובת הזו), נבחר את הכתובת שבה אנחנו מתחברים באתר שלנו לפייסבוק (במידה וכרגע אנחנו עובדים בלוקל הוסט - פשוט נכתוב זאת כך), כי הפייס מבצע התאמה מלאה בין הכתובת שנשלחת בעת ההתחברות באתר לבין ההגדרות של בעל האתר.

1. ניתן להעתיק את הקוד שאתר של הפייסבוק מציג
בממשק ניתן לשנות את הכפתור: גודל, שורות, חיבור של החברים שאהבו את זה.

הבעיה הרצינית בסיפור הזה, שמחד זה מאוד פשוט להטמיע ולהשתמש. אבל! החיבור מבצע POSTBACK, ואם ביצענו שינויים ברמת הקליינט הם יעלמו לאחר החיבור. אפשר לבצע פתיחת חלון חדש להתחברות, ואז ממש לא משנה הPOSTBACK.

2. אפשר לכתוב קוד שיבקש אישור מהמשתמש לקבל את הפרטים שלו. לשם כך צריך לבקש מהמשתמש  במידה והוא מאשר את הפרטים שאותם אנחנו רוצים לקבל ממנו. נקבל Access Token שהוא אישור כניסה שניתן לאחר התחברות בפקודת JS הבאה:
var accessToken = response.authResponse.accessToken;

בשביל לחוש את הדברים שוב נכנס לAPI של הפייסבוק, והפעם נעשה get access token ונקבל המון מידע.

דוגמא לקוד זה נמצא פה. או גירסא דומה:
<div class="fb-login-button" autologoutlink="true" scope="user_birthday,email">
                        התחבר עם פייסבוק

</div>

בשביל להבין את הדברים היטב, צריך להבין שלאחר האישור הראשוני, כל כניסה אל האתר מאושרת אוטומטית.לכן בעת הכניסה בפעם הבאה מיד נקבל את כל הפרטים שנדרוש מהפייסבוק על סמך האישור שניתן פעם.  אם נרצה לבטל את האישור לאתר נצטרך בהגדרות של פייסבוק האישי לבטל את החיבור. ביצוע LOGOUT ינתק את המשתמש מהאתר, וגם מהפרופיל שלו! הוא יצטרך בכניסתו לפייסבוק לבצע כניסה מחדש.

קוד באתר שמבצע הכנסה של תמונה אוטומטית. המחשב מזהה שהמשתמש מחובר לפרופיל שלו לפייסבוק לוקח את הID ומציג את התמונה באתר.
ראשית, נוסיף לאתר DIVfb-root שעליו בונה הפייס את ההגדרות שלו, במידה ולא נוסיף פקודות הJS של הפייס יעשו זאת בעצמם, ואת הDIV  toolBar שבו תופיע התמונה
<!---Facebook--->
   <div id="fb-root"></div>
  <div id="toolBar"></div>

<!---end Facebook--->

<script>
 window.fbAsyncInit = function () {
            FB.init({
                appId: '****************', // App ID
                scope: 'id,name,gender,email,user_birthday',
                status: true, // check login status
                cookie: true, // enable cookies to allow the server to access the session
                frictionlessRequests: true,
                version: 'v2.2', // use version 2.1
                xfbml: true  // parse XFBML
            });

            FB.Event.subscribe('auth.statusChange', function (response) {
                //alert(response.status + " Event");
                if (response.status == 'connected') {
                    // user has auth'd your app and is logged into Facebook
                    FB.api('/me', function (me) {
                        if (me.name) {
                            LoadImgUser(me);
                        }
                    })
                } else if (response.status === 'not_authorized') {
                    // The person is logged into Facebook, but not your app. 
                    // user has not auth'd your app, or is not logged into Facebook
                } else {
                    // The person is not logged into Facebook, so we're not sure if they are logged into this app or not.
                }
            });
        };

        // Load the SDK asynchronously
        (function (d, s, id) {
            var js, fjs = d.getElementsByTagName(s)[0];
            if (d.getElementById(id)) { return; }
            js = d.createElement(s); js.id = id;// js.async = true;
            js.src = "//connect.facebook.net/en_US/sdk.js";
            fjs.parentNode.insertBefore(js, fjs);
        }(document, 'script', 'facebook-jssdk'));

        function LoadImgUser(me) {
            ////add to client
            var toolBar = document.getElementById("toolBar");
                img = document.createElement('img');
                img.src = "http://graph.facebook.com/" + me.id + "/picture?type=small";
                img.id = "profileImg";
                toolBar.appendChild(img);
        }


<script/>


דוגמא לLOGOUT, שתי הדוגמאות מבצעות את אותה פעולה, ניתוק המשתמש אפילו מהפרופיל שלו:
function logoutFB() {
           window.location = "https://www.facebook.com/logout.php?next=" + url + "&access_token=" + accessRes.accessToken;


            FB.logout(function (response) {//log out olso user
                // user is now logged out
            });        }

את הנתון accessToken שמרנו בעת ההתחברות אל הדף

יום רביעי, 22 באוקטובר 2014

יצירת SiteMap.xml מתוך DB


לאחר יצירת דף XML בROOT של האתר, הקוד ישאב את שמות הכתבה והתאריך יצירה שלהם מהנתונים בDB (לאחר שיוסיף להם את הכתובת של האתר והסיום aspx)

using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data.SqlClient;
using System.Linq;
using System.Web;
using System.Xml;

/// <summary>
/// Summary description for siteMap
/// </summary>
public class siteMap
{
       public siteMap() {}

    public class nodeXmlSiteMap{
        private string _loc = "http://www.mysite.co.il/";
        public string loc { get { return _loc; } set { value = value + ".aspx"; _loc = value; } }
        private string _priority = "1.0";
        public string priority { get {return _priority; } set {_priority = value; } }
        private string _changefreq = "Monthly";
        public string changefreq { get { return _changefreq; } set { _changefreq = value; } }
        private string _lastmod = String.Format("{0:yyyy-MM-dd}", DateTime.Now);
        public string lastmod {get {return _lastmod;} set { _lastmod = value; }
        }
    }

    public void createFileSiteMap() {

        XmlTextWriter writer = new XmlTextWriter(HttpRuntime.AppDomainAppPath + "sitemap.xml", System.Text.Encoding.UTF8);
            writer.WriteStartDocument(true);
            writer.Formatting = Formatting.Indented;
            writer.Indentation = 4;
            writer.WriteStartElement("urlset", "http://www.sitemaps.org/schemas/sitemap/0.9 ");

            writer = getDataFromDB(writer);
            writer.WriteEndElement();
            writer.WriteEndDocument();
            writer.Close();
        }

    private XmlTextWriter getDataFromDB(XmlTextWriter writer)
    {
       
        SqlConnection con = new SqlConnection(ConfigurationManager.ConnectionStrings["Con"].ConnectionString);
        SqlCommand cmd;
       
        try
        {
             cmd = new SqlCommand("SELECT UrlArticle,DateArticle FROM Article");
             cmd.Connection = con;
             cmd.CommandType = System.Data.CommandType.Text;
          
             con.Open();
             SqlDataReader dr = cmd.ExecuteReader();

                while (dr.Read())
                {
                    nodeXmlSiteMap url = new nodeXmlSiteMap();
                    url.loc = url.loc + dr[0].ToString();
                   // url.priority ="1.0";
                   //url.changefreq = "Monthly";
                    DateTime _myDate = new DateTime();
                        _myDate = (DateTime)dr[1];
                        url.lastmod = String.Format("{0:yyyy-MM-dd}", _myDate);
                    createNode(url, writer);
                }
            con.Close();

        }
        catch (Exception ex)
        {

        }
        return writer;
    }

    private void createNode(nodeXmlSiteMap url, XmlTextWriter writer)
    {
        writer.WriteStartElement("url");
            writer.WriteStartElement("loc");
            writer.WriteString(url.loc);
            writer.WriteEndElement();
            writer.WriteStartElement("lastmod");
            writer.WriteString(url.lastmod);
            writer.WriteEndElement();
            writer.WriteStartElement("changefreq");
            writer.WriteString(url.changefreq);
            writer.WriteEndElement();
            writer.WriteStartElement("priority");
            writer.WriteString(url.priority);
            writer.WriteEndElement();
        writer.WriteEndElement();
    }          

}

יום ראשון, 2 בפברואר 2014

ASP.NET Web API

כתיבת web api בפרויקט ASP.NET

הפרוייקט שאני כותב עכשיו עוסק בממשק פשוט שמקבל אדם שמשלם כסף, וישנה אפשרות לבחור מזומן או צק. במידה וייבחר צק ניתן להזין את הצקים שבהם הלקוח שילם.
תמיד עבדתי עם הנדלרים להעביר את הנתונים ומכיוון שנראה לי די טיפשי להעביר עוד מתווך בין הקליינט לסרבר,

בדקתי את שלושת האפשרויות הפופולאריות:
- WCF  שנראה לפי המדריכים שמיועד לדברים יותר רציניים, והוא יותר כבד מליישומי אינטרנט פשוטים.
- WEB SERVER  שממש קל ליישום, אבל הבעיה שכל הפונקציות שמקבלות את הנתונים הם static, שזו פונקציה יחידנית שאיננה מתקשרת עם פונקציות אחרות. וגם כל פונקציה שיורשת ממנה או ממשיכה ממנה הינה static. הרגשתי שזה מגביל אותי.
לכן בחרתי לעבוד עם web api  שמתקשרת רק על בקשות http. שנראה די פשוט, ומאוד פשוט לתפעול עם מנגנון הפניית הכותבות (route).

הצרות התחילו שמסתבר שכל הדוגמאות, הפרויקטים הינם הMVC, דבר שמעודד מחד - כי זה אומר שזה חלק מהעתיד, מצד שני לא מצאתי פרויקט נורמאלי אחד שגם עובד בASPNET (למה אני מתקשה לעבוד בMVC זה נושא לפוסט אחר).
החלטתי לכתוב מדריך שבעצם ילווה את תהליך הלמידה שלי, ולכן כמובן הכל בעירבון מוגבל.
חרישת הרשת העלתה פרוייקט אחד, שמהווה פרויקט רציני שגם נראה שעובד בgithub. שעיקר הדוגמא מתבססת עליו. כמובן, בצורה הרבה יותר פשוטה.
והסבר לפרוייקט בצורה מאירת פנים
http://weblog.west-wind.com/posts/2012/Aug/21/An-Introduction-to-ASPNET-Web-API

בשלב הראשוני, נקבל פרוייקט שנראה כך: 





לאחר שפתחנו פרוייקט רגיל של asp.net, ננתח את המחלקה של התשלום ואת האופן שבו אנחנו רוצים להשתמש בה. נוסיף מחלקה חדשה:
חשוב. חובה שזה יהיה בתקיית App_Code אחרת המחלקות לא יזוהו בפרוייקט.
public class payStu
{

    public int idPay { get; set; }//PK
    public DateTime datePay { get; set; }
    public int idSchool { get; set; }
    public int idSnif { get; set; }
    public int idSecretary { get; set; }//id name in the login system


    public string namePay { get; set; }
    public string nameStu { get; set; }

    public int paySum { get; set; }
    public string payDis { get; set; }
    public string payType { get; set; } //1- cash num-idzkList
    public string payFor { get; set; }

   public virtual List<zk> zkList { get; set; }

   public payStu()
   {
       zkList = new List<zk>();
   }

   public void AddZK(int idzkList, int zkBankNum, int zkBranchNum, int zkAccountNum, decimal zkSum)
   {
       this.zkList.Add(new zk()
       {
           idzkList = idzkList,
           zkBankNum = zkBankNum,
           zkBranchNum = zkBranchNum,
           zkAccountNum = zkAccountNum,
           zkSum = zkSum
       });
   }
}
כיוון שייתכן שנקבל צ'ק, נצטרך לבנות מחלקה נוספת לצ'קים. הקישור אליהם ייעשה דרך payType. ערך 1 מהווה הוכחה שזהו מזומן, אחרת יהיה שם תיאור של ID של הצק. (כל קבוצת הצ'קים שימסרו על ידי הלקוח באותו מעמד יקבלו אותו ID , כך שנוכל לקשר את כל הצקים לאותה עסקה).
public class zk
{
       public zk()
       {}

    public int idzkList { get; set; }
    public int zkBankNum { get; set; } // num of bank
    public int zkBranchNum { get; set; } // num of snif bank
    public int zkAccountNum { get; set; } //num of account
    public decimal zkSum { get; set; }

}
================================
כעת, ניצור את דף הגלובלי Global.asax. ניצור את הכללים שבהם האתר ינתח את כתובת הurl שמגיעות אליו.

using System.Web.Http;
using System.Web.Routing;
using System.Web.Http;
using System.Net.Http;
using System.Web.Http.WebHost;
public class Global : System.Web.HttpApplication
    {

        protected void Application_Start(object sender, EventArgs e)
        {
            RouteTable.Routes.MapHttpRoute(
                name: "API",
                routeTemplate: "pay/{ payDi }",
                defaults: new { payDi = System.Web.Http.RouteParameter.Optional }
            );

        }


אפשרות נוספת היא להגדיר פעולה ברירת מחדל, תחת המשתנה defaults, באופן שכזה:
 defaults: new { action = "functionDefaults", payDi = System.Web.Http.RouteParameter.Optional }
ואז בקונטרולר, נוסיף את השדה [ActionName("functionDefaults")] מעל הפונקציות שאנחנו רוצים לשייך לפעולה הדיפולטיבית.
לדו'
[HttpPost]
[ActionName("functionDefaults")]
        public void Post(int t)
        {
           
        }

במידה ופתחנו פרויקט אינטרנט, נצטרך להוסיף ב Global.asax
<%@ Import Namespace="System.Web.Routing" %>
<%@ Import Namespace="System.Web.Http" %>
<%@ Import Namespace="System.Net.Http" %>
<script runat="server">

    void Application_Start(object sender, EventArgs e)
    {
        // Code that runs on application startup
RouteTable.Routes.MapHttpRoute(
name: "API",
routeTemplate: "api/{controller}/{payDi}",
defaults: new { payDi = System.Web.Http.RouteParameter.Optional });
    }
</script>
לname API אין שום משמעות, אלא לתאר את תפקיד המפקח. יותר חשוב הכלל routeTemplate: "pay/{title}", שמורה שכל כתובת URL שיש בה את "תיקייה" pay ולאחריה כתובת יש לטפל בה. לדו' www.pay.com/pay/choen

- במידה וישנן בעיות עם הספריות. נוסיף ברשימת פקודות
Install-Package Microsoft.AspNet.WebApi -Version 4.0.30506
או רפרנסים לספריות שחסרות.


========================================
לאחר מכן נוסיף את controller (מפקח) שהוא מבצע את הטיפול בפניות. יש לשים לב ששמו יסתיים בcontroller כדי שהשרת יוכל לזהות אותו כמפקח. לפרויקט נוסיף אייטם חדש:




ונקרא לו payController.
ניתן לפתוח תיקייה של Controller, ולאסוף שם את כל המפקחים.
בקובץ web.config יתווסף לנו:
<configuration>
  <system.web>
    <compilation debug="true" targetFramework="4.5">
      <assemblies>
        <add assembly="System.Net.Http, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B03F5F7F11D50A3A" />
        <add assembly="System.Net.Http.WebRequest, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B03F5F7F11D50A3A" />
      </assemblies>
    </compilation>
    <httpRuntime targetFramework="4.5" />
  </system.web>
<system.webServer>
    <handlers>
      <remove name="ExtensionlessUrlHandler-ISAPI-4.0_32bit" />
      <remove name="ExtensionlessUrlHandler-ISAPI-4.0_64bit" />
      <remove name="ExtensionlessUrlHandler-Integrated-4.0" />
      <add name="ExtensionlessUrlHandler-ISAPI-4.0_32bit" path="*." verb="GET,HEAD,POST,DEBUG,PUT,DELETE,PATCH,OPTIONS" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness32" responseBufferLimit="0" />
      <add name="ExtensionlessUrlHandler-ISAPI-4.0_64bit" path="*." verb="GET,HEAD,POST,DEBUG,PUT,DELETE,PATCH,OPTIONS" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework64\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness64" responseBufferLimit="0" />
      <add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="GET,HEAD,POST,DEBUG,PUT,DELETE,PATCH,OPTIONS" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />
    </handlers>
  </system.webServer>
</configuration>

נסתכל לדוגמא באחד מן הפונקציות האוטומטיות שנכתבו
// POST api/<controller>
        public void Post([FromBody]string value)
        {
        }
כל פקודה שנשלחת מהקליינט בPOST תטופל בפקודה זו.
במידה ונשלח פקודת GET, ישנן 2 אפשרויות, שהמפקח מטפל בפונקציות לפי הבקשה:
// GET api/<controller>
        public IEnumerable<string> Get()
        {
            return new string[] { "value1", "value2" };
        }

        // GET api/<controller>/5
        public string Get(int id)
        {
            return "value";
        }
במידה וזו תהיה רק בקשה GET, המפקח ישלח את כל הנתונים "value1", "value2", במידה ובבקשת הGET יהיה לאחר שם מפקח גם מספר, הוא יזהה שהכוונה למזהה ID ויתן לנו ערך התואם לid המבוקש.
כל בקשה שנשלחת בGET   מגיעה לכל הפונקציות שמתחילות בGET, GetPay GetZK וכו'.

נבנה את המפקח שיטפל בנתונים
    public void Post(payStu pay)
    {
    }


=========================
כעת נתחיל לעבוד על הקליינט, נוסיף דף ראשי defult.aspx 
נוסיף את ספריית ה jQuery, לחיצה על הפרוייקט' לחצן ימני ועם NuGet נוסיף את הספרייה jquery (זו הספרייה הכי פופולרית, לכן רוב הסיכויים שתהיה ראשונה). 
הפרוייקט גם יעדכן אותנו שהוא מעדכן את קובץ ה config.web.
נוסיף את הספרייה לתוך הפרוייקט, פשוט נגרור את הקובץ לתוך הפרוייקט אחרי הtitle
<script src="Scripts/jquery-2.1.0.min.js"></script>
    <script type="text/javascript">
        var pay = {
            "idPay": 0,
            "namePay": "mose",
            "nameStu": "choen",
            "paySum":"100",
            "payDis":"מאה שקלים חדשים",
            "payType": 1234,
            "payFor": "תשלום חודשי למוסד",
            "zkList": [
                { "idzkList": 1234, "zkBankNum": 3, "zkBranchNum": 6767, "zkAccountNum": 1212, "zkSum": 2323.04 },
                { "idzkList": 1234, "zkBankNum": 32, "zkBranchNum": 5656, "zkAccountNum": 7877, "zkSum": 78.64 },
                { "idzkList": 1234, "zkBankNum": 32, "zkBranchNum": 5656, "zkAccountNum": 676, "zkSum": 8989 }
            ]
        }

        $.ajax({
            type: 'POST',
            url: "api/pay/",
            contentType: "application/json",
            data: JSON.stringify(pay),
            success: function (result) {
                alert(result);
            }
        });
    </script>
את הנתונים של התשלום, ואת הצקים נשייך לרשומה 1234, שתהווה את "המקבץ" של הצקים ששולמו בתשלום הנוכחי. נריץ את הפרוייקט ונוודא שאכן כל המשתנים הגיעו לשרת.


===============================
תקלות שעלולות לצוץ:
- אני לא יודע למה, אבל אין לשים את הדף בתיקיה חוץ מהroot הראשי - לפחות אצלי, הפרויקט לא עבד.
- בקובץ הקונפיגרציה, הקובץ DLL של הרוטינה נטען כך:
    <!--הוספת DLL-->
    <compilation debug="true" targetFramework="4.0">
      <assemblies>
        <add assembly="System.Net.Http, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
      </assemblies>

    </compilation>
משום מה, היה רגעים שהVS לא זיהה את הקובץ DLL, וטען שאני צריך לעדכן. אז צריך להוסיף גם את ההגדרות האלה תחת התגית configuration:
  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <dependentAssembly>
        <assemblyIdentity name="System.Net.Http" publicKeyToken="b03f5f7f11d50a3a" />
        <bindingRedirect oldVersion="0.0.0.0-4.0.0.0" newVersion="4.0.0.0" />
      </dependentAssembly>
    </assemblyBinding>
  </runtime>