Wilfried Woivré & .Net

Silverlight & WCF : Interroger un service via une url relative

JUIN23

Quand on développe une application Silverlight, on a souvent besoin de s’abonner à des services afin d’accéder aux données situées sur un serveur distant, on créé donc le plus généralement un service WCF qui exposera nos données.

Si vous n’utilisez pas IIS pour vos développements, vous avez du remarquer, du moins je l’espère, que Visual Studio démarre un serveur web allégé qui hébergera votre application. On accède donc à notre application via une url de ce type http://localhost:24421/MaSuperApplicationQuiDeboiteTestPage.aspx jusque là aucun problème votre application marche et vous avez accès à votre service via cette url : http://localhost:24421/MonSuperServiceQuiDeboite.svc.

 

Donc quand vous vous abonnez au service via Visual Studio, il va vous générer le proxy, ainsi qu’un fichier de config avec les données de configuration du service, comme on peut le voir ci-dessous :

<configuration>
    <system.serviceModel>
        <bindings>
            <basicHttpBinding>
                <binding name="BasicHttpBinding_IService1" maxBufferSize="2147483647"
                    maxReceivedMessageSize="2147483647">
                    <security mode="None" />
                </binding>
            </basicHttpBinding>
        </bindings>
        <client>
            <endpoint address="http://localhost:20624/Service1.svc" binding="basicHttpBinding"
                bindingConfiguration="BasicHttpBinding_IService1" contract="ServiceReference1.IService1"
                name="BasicHttpBinding_IService1" />
        </client>
    </system.serviceModel>
</configuration>

Le problème que l’on peut cependant obtenir et que Visual Studio, selon son humeur, peut décider de changer le port de votre application, et donc vos informations de connexion seront incorrectes. Vous pouvez donc soit fixer le port du serveur web, soit changer ces informations comme on va le voir par la suite. Plus concrètement, ce problème arrive très souvent lorsque vous faites du développement pour Azure avec l’émulateur qui vous génère une url de ce type http://127.0.0.1:81, le port peut changer si vous quittez l’appli brutalement, typiquement lorsque vous avec un point d’arrêt déclenché dans votre Visual Studio et que vous arrêtez le debug !

 

Alors pour éviter le soucis, il vous suffit de mettre votre adresse de service en relative, comme ci dessous

<configuration>
    <system.serviceModel>
        <bindings>
            <basicHttpBinding>
                <binding name="BasicHttpBinding_IService1" maxBufferSize="2147483647"
                    maxReceivedMessageSize="2147483647">
                    <security mode="None" />
                </binding>
            </basicHttpBinding>
        </bindings>
        <client>
            <endpoint address="/Service1.svc" binding="basicHttpBinding"
                bindingConfiguration="BasicHttpBinding_IService1" contract="ServiceReference1.IService1"
                name="BasicHttpBinding_IService1" />
        </client>
    </system.serviceModel>
</configuration>

C’est beaucoup mieux que de reconstruire l’url du service à partir de l’url courante !

Remonter

Partager des classes métiers entre plusieurs services WCF

JUIN17

Lorsque vous développez des applications Silverlight ou pour Windows Phone 7, il est très courant d’utiliser des Web Services en WCF afin d’exposer nos données métiers depuis les différents serveurs qui les héberge (sous Azure, c’est mieux ^^)

Vous pouvez concevoir vos accès aux données de plusieurs manières avec WCF, par exemple :

  • Un seul service WCF qui regroupera toutes les méthodes exposées
  • Plusieurs services WCF qui se répartissent les méthodes selon des critères de fonctionnalités (Authentification, Processus Métier A, Processus Métier B …)

On peut voir que le premier cas est très bien pour un effet “démo” ou une petite application, alors que le deuxième cas apporte une structure logique au web service et pour ne rien gâcher permet de répartir la charge entre les différents services WCF.

Bon cependant, vu qu’on n’est plus dans une démonstration, il y a des cas auxquels on ne pense pas de suite, par exemple, un Service A peut devoir utiliser les mêmes classes métiers qu’un Service B tous les deux référencés dans une même application.

Si l’on effectue, un ajout de service standard, on va faire simplement un clic droit sur le projet auquel on veut ajouter notre service, puis un “Add Service Reference”, on obtient une fenêtre telle que celle-ci

image

On va donc pouvoir ajouter nos deux services de la même façon.

J’ai donc reproduit dans mon application le cas dont je parlais, mes deux services utilisent ces contrats réciproquement

using System.ServiceModel;
using WCFSharedClass.Model;

namespace WCFSharedClass.Web
{
    // NOTE: You can use the "Rename" command on the "Refactor" menu to change the interface name "IService1" in both code and config file together.
    [ServiceContract]
    public interface IService1
    {
        [OperationContract]
        Class1 DoWork();

        [OperationContract]
        Class2 DoWork2();
    }
}
using System.ServiceModel;
using WCFSharedClass.Model;

namespace WCFSharedClass.Web
{
    // NOTE: You can use the "Rename" command on the "Refactor" menu to change the interface name "IService2" in both code and config file together.
    [ServiceContract]
    public interface IService2
    {
        [OperationContract]
        void DoWork3(Class1 class1, Class2 class2);
    }
}

On voit donc que mes deux services utilisent les classes Class1 et Class2, on a donc dans mon application cliente (ici Windows Phone 7.0) ce code pour appeler nos services

private Service1.Class1 _class1;
private Service1.Class2 _class2;

private void Service1()
{
    var service1Client = new Service1.Service1Client();
    service1Client.DoWorkCompleted += (sender, e) =>
                                          {
                                              _class1 = e.Result;
                                              MessageBox.Show("Service 1 - DoWork");
                                          };
    service1Client.DoWorkAsync();


    service1Client = new Service1.Service1Client();
    service1Client.DoWork2Completed += (sender, e) =>
                                           {
                                               _class2 = e.Result;
                                               MessageBox.Show("Service 1 - DoWork2");
                                           };
    service1Client.DoWork2Async();
}

private void Service2()
{
    var service2Client = new Service2.Service2Client();
    service2Client.DoWork3Completed += (sender, e) => MessageBox.Show("Service 2 - DoWork3");
    service2Client.DoWork3Async(new Service2.Class1() { Value = _class1.Value }, new Service2.Class2() { Value = _class2.Value });
}

 

J’ai mis en gras les différentes instances de Class1 et Class2 et on peut voir que lorsqu’on utilise le Service1, la Class1 se situe dans le namespace Service1.Class1 alors que dans l’autre service elle se trouve dans Service2.Class1. On voit donc que pour appeler le Service2.DoWork3 avec les différents résultats du Service1 on est obligé de reconstruire les différents objets dont on a besoin. Même si ici, on n’a qu’un seul champ, on voit que ce n’est pas pratique. Le mieux est donc d’utiliser les mêmes Class1 et Class2 dans tous nos services !

Pour faire cela, il faut commencer par créer une bibliothèque de classe du côté client donc ici Windows Phone 7, et créer nos deux class de Model avec des propriétés publiques égales à celle exposées par nos Services, on a donc deux choix, le premier est une copie strictement identique, en ajoutant un fichier existant comme lien, comme on peut le voir ci dessous

image

 

L’avantage de faire ainsi, c’est que le code sera strictement identique du côté client, comme du côté serveur, cependant, si on utilise un objet côté serveur qui n’existe pas côté Windows Phone, on ne pourra pas compiler, mais vous pouvez facilement éviter cela avec des classes partielles.

L’autre technique est de copier séparément les fichiers comme j’ai fait pour la Class2, vous pouvez ainsi les modifier tant que les propriétés publiques exposées existes toujours

Côté Serveur :

using System.Runtime.Serialization;

namespace WCFSharedClass.Model
{
    [DataContract]
    public class Class2
    {
        [DataMember]
        public string Value { get; set; }
    }
}

Côté client :

namespace WCFSharedClass.Model
{
    public class Class2
    {
        private string _value;
        public string Value
        {
            get { return _value; }
            set { _value = value; }
        }
        public override bool Equals(object obj)
        {
            if (obj is Class2)
            {
                return Value == ((Class2) obj).Value;
            }
            return false;
        }

        public override int GetHashCode()
        {
            return this.Value.GetHashCode();
        }
    }
}

Note dans Visual Studio, on peut facilement voir qu’une classe est ajoutée comme lien, grâce au petit icône à côté de celle-ci.

image

Maintenant il suffit de reconfigurer vos services en utilisant votre assembly  contenant vos différentes classes.

image

Il vous suffit donc de faire cela pour tous vos services, les régénérer, compiler et recommencer si Visual Studio est grincheux ….

Et voilà donc le code final :

private Class1 _class1;
private Class2 _class2;

private void Service1()
{
    var service1Client = new Service1.Service1Client();
    service1Client.DoWorkCompleted += (sender, e) =>
                                          {
                                              _class1 = e.Result;
                                              MessageBox.Show("Service 1 - DoWork");
                                          };
    service1Client.DoWorkAsync();


    service1Client = new Service1.Service1Client();
    service1Client.DoWork2Completed += (sender, e) =>
                                           {
                                               _class2 = e.Result;
                                               MessageBox.Show("Service 1 - DoWork2");
                                           };
    service1Client.DoWork2Async();
}

private void Service2()
{
    var service2Client = new Service2.Service2Client();
    service2Client.DoWork3Completed += (sender, e) => MessageBox.Show("Service 2 - DoWork3");
    service2Client.DoWork3Async(_class1, _class2);
}
On utilise donc les mêmes classes pour nos deux services, ce qui est tout de même plus pratique ! 
A noter que vous avez à la fenêtre de configuration avancée via le bouton “Advanced” dans l’enregistrement du service !
Je ne vous fournis pas le code, tout est là ! 
Remonter

WCF RIA Services et les Complex Type d’Entity Framework

MAI1

Actuellement, je suis en train de réaliser un projet pour une formation Silverlight, le but de ce projet est de réaliser un mini site eCommerce totalement en Silverlight. Il s’articule autour des technologies Entity Framework et RIA Services ainsi que Silverlight 4 avec un design pattern de type MVVM. J’utilise comme base de données AdventureWorksLT2008. Je donnerais tout le code de cette démonstration sur mon blog un autre jour, avec une explication un peu plus conséquente !

Enfin passons, pour ma vie, dans cet article je vais vous montrer comment renvoyer un Complex Type crée depuis EntityFramework et renvoyer dans notre Silverlight.

Je commence donc par créer un projet de type Silverlight Business Application, ensuite je crée un modèle Entity Framework, ici, on se basera uniquement sur la table Employees de NorthWind.

Sur cette table, je vais vouloir récupérer uniquement les noms et les prénoms des employés, afin de les renvoyer à mon application Silverlight, je vais donc créer un ComplexType contenant deux chaines de caractères.

J’en suis donc arrivé à un diagramme ressemblant à ceci :

image

Maintenant créons notre Domain Service, et notre requête Linq qui ressemble à cela :

[EnableClientAccess()]
public class NortwindDomainService : LinqToEntitiesDomainService<NorthwindEntities>
{

// TODO: Consider
// 1. Adding parameters to this method and constraining returned results, and/or
// 2. Adding query methods taking different parameters.
public IQueryable<EmployeeInformation> GetEmployees()
{
return from e in ObjectContext.Employees
select new EmployeeInformation() { FirstName = e.FirstName, LastName = e.LastName };
}
}

On s’apprête donc à créer une interface pour afficher les données en Silverlight, sauf qu’en compilant on obtient une erreur :

image

Il manque donc une clef à notre complex type, qu’à cela ne tienne, pour en rajouter une, il suffit de créer une classe partielle à notre type et d’y ajouter une clé.

public partial class EmployeeInformation
{
[Key]
[DataMember]
public int EmployeeInformationId { get; set; }
}

Maintenant que ce problème est réglé et qu’on a bien entendu ajouter la déclaration de notre clé dans notre requête, il faut passer au front, on va donc créer un liste qui affiche les noms et les prénoms des employés

On obtient un code de ce style :

public IEnumerable<Entity> Employees
{
get { return (IEnumerable<Entity>)GetValue(EmployeesProperty); }
set { SetValue(EmployeesProperty, value); }
}

// Using a DependencyProperty as the backing store for Employees.  This enables animation, styling, binding, etc...
public static readonly DependencyProperty EmployeesProperty =
DependencyProperty.Register("Employees", typeof(IEnumerable<Entity>), typeof(MainPage), new PropertyMetadata(null));

public MainPage()
{
InitializeComponent();
this.DataContext = this;
this.Loaded += (sender, e) => LoadData();
}

private void LoadData()
{
NortwindDomainContext context = new NortwindDomainContext();
LoadOperation loadOp = context.Load(context.GetEmployeesQuery());
Employees = loadOp.Entities;
}

On charge donc le tout dans une liste que l’on expose à notre interface, le tout compile sans aucun problème on s’attend donc à voir un résultat, cependant on obtient encore une erreur :

image

On a donc une exception au niveau de notre DomainService, la requête quand à elle est bonne, le seul problème est en fait une erreur d’Entity Framework, on ne peut pas créer de Complex Type dans une requête Linq To Entities.

Il nous faut donc passer via un objet anonyme pour pouvoir ensuite instancier notre complex Type.

public IQueryable<EmployeeInformation> GetEmployees()
{
var employees = from e in ObjectContext.Employees
select new { e.EmployeeID, e.FirstName, e.LastName };

return employees.ToList().ConvertAll(e => new EmployeeInformation()
{
EmployeeInformationId = e.EmployeeID,
FirstName = e.FirstName,
LastName = e.LastName
}).AsQueryable();

}

On obtient donc par la suite nos différentes données :

image

Et voilà en espérant que cette technique vous sera utile dans vos futurs développements avec RIA Services ! En plus je vous fournis tout le code source !

image

Remonter

Intégrer des images via RIA Services

AVRI2

 

Il m’est récemment arrivé lors d’une démonstration de vouloir sortir les images de NorthWind pour les inclure dans un composant Silverlight.

Donc naïvement, je me suis dis qu’un simple “converter” binaire suffira pour afficher mon image. “Converter” que voici :

public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
    BitmapImage image = new BitmapImage();

    using(MemoryStream stream = new MemoryStream(value as byte[]))
    {
        image.SetSource(stream);
    }

    return image;
}

Et bien entendu lors de l’exécution on a donc une petite erreur qui n’aide pas trop en fait ….

image

Bon après quelques réflexions, on se dit que le format de l’image n’est pas correct, où alors qu’il y a un problème avec le flux de données.

La solution que j’ai trouvé est d’enregistrer l’image sur un serveur de medias, et de passer l’url de celle-ci à Silverlight. Comme ça on gagne au niveau du Converter, et de plus on m’a toujours déconseillé de stocker mes images en bases de données pour la place qu’elles utilisent.

Le plus simplement possible, en faisant cela directement dans mon fichier DomainService,

private IQueryable<Category> CategoryWithPictureUrl(IQueryable<Category> categories)
{
    foreach (var category in categories)
    {
        if (category.Picture != null)
        {
            TypeConverter tc = TypeDescriptor.GetConverter(typeof(System.Drawing.Bitmap));
            System.Drawing.Bitmap b = (System.Drawing.Bitmap)tc.ConvertFrom(category.Picture);
            if (b != null)
            {
                String path = String.Format(@"D:\Medias\Category{0}.jpg", category.CategoryID);
                if (!File.Exists(path))
                {
                    b.Save(path, System.Drawing.Imaging.ImageFormat.Jpeg);
                    b.Dispose();
                }
                category.PictureUrl = String.Format("http://localhost/Media/Category{0}.jpg", category.CategoryID);
            }
        }
    }
    return categories.AsQueryable();
}

La propriété PictureUrl est ajouté dans une classe partielle de l’entité Category de la façon suivante :

public partial class Category
{
    [DataMemberAttribute]
    public string PictureUrl { get; set; }
}

Et voilà, comme ça on peut voir nos images directement depuis Silverlight !

Pensez aussi à ne pas envoyer votre binaire histoire d’alléger un peu votre service.

Bien entendu, ce fonctionnement marche très bien sans RIA Services !

Remonter

Partager une propriété “partielle” via WCF RIA Services

AVRI2

 

J’ai présenté dans un précédent article la méthode pour ajouter une propriété à une entité issue d’un modèle Entity Framework (ou Linq To Sql) –> http://wilfriedwoivre.wordpress.com/2009/03/21/entity-framework-trucs-et-astuces/

Pour le résumé, on ajoute une propriété Total via une classe partielle sur la classe Order.

public partial class Order
{
    public decimal Total
    {
      get { return Order_Details.Sum(n => n.Quantity * n.UnitPrice); }
    }
}

Maintenant si on essaye de faire la même chose, afin de récupérer notre Total via RIA Services on s’aperçoit qu’on a un léger problème lorsqu’on regarde les différentes sources de données disponibles dans Silverlight via RIA Services.

image

Afin de rajouter notre propriété personnalisée dans notre composant Silverlight, il suffit simplement de rajouter un attribut DataMemberAttribute sur Total.

public partial class Order
{
    [DataMemberAttribute]
    public decimal Total
    {
      get { return Order_Details.Sum(n => n.Quantity * n.UnitPrice); }
    }
}

 

En fait, il suffit simplement d’ajouter cet attribut, puis que WCF RIA Services est comme son nom l’indique basé sur un service WCF.

Remonter