现在的位置: 首页 > 综合 > 正文

SCSF – Part 8 Creating and Using Services in the CAB

2013年12月07日 ⁄ 综合 ⁄ 共 13101字 ⁄ 字号 评论关闭

Introduction

Part 7 of this series of articles gave us a general introduction to services in the CAB. This article will go into more detail on the various ways we can create and use such services.

part7 给了我们一个大致的关于CAB里的service的介绍,这篇文章将会告诉我们更多的细节和一些不同的创建并且使用service的方法

 

Ways of Creating a Service

We start with the various ways services can be created. This can be done with the various ‘Add’ methods, with XML configuration files or by using the ‘Service’ attribute.

我们从不同的创建service的方法开始, 可以通过 ‘Add’ 方法,XML configuration files ,或者通过加 ‘Service’ attribute.

Ways of Creating a Service (1) – Add Methods

In the basic example in part 7 we used the AddNew method to create a service:

            RootWorkItem.Services.AddNew<MyService>();
part7介绍了用addnew方法创建service

We have seen this before: it both instantiates the object and adds it to the collection. As before, we can also add objects that already exist to the Services collection with the Add method.

我们已经看到过以上代码 既创建了一个对象,并且把他加入到了collection,就像以前一样,我们也可以直接把已经存在的对象通过Add方法加入到Services collection

 

The Services collection also has an ‘AddOnDemand’ method. If we use this in place of AddNew in the example in part 7 the service does not immediately get created (the MyService class is not instantiated). Instead a placeholder is added to the Services collection until such time as some client code retrieves the service (using the same syntax as before). When this happens the service object will get instantiated so that it can be used.

Services collection 还有一个‘AddOnDemand’  方法 在这个例子里用于取代AddNew 方法从而使得这个service 并没有马上被创建,而是一个placeholder 被加入到了Services collection ,直到有客户程序调用这些服务。

This example shows this:

// Use AddOnDemand to set up the service: the MyService constructor
// is not called
RootWorkItem.Services.AddOnDemand<MyService>();
// When we dislay the Services collection we can see there's a placeholder
// for MyService in there
DisplayWorkItemCollections(RootWorkItem);
// Only when we use .Get to retrieve the service is MyService actually
// instantiated (note we have code in MyService to show when the constructor
// is called by writing to the Output window)
UseMyService();
// Now our Services collection has a fully fledged MyService service available
DisplayWorkItemCollections(RootWorkItem);

There are also Contains and Remove methods on the Services collection. Remember we can only have one service of a given type: if a service already exists and we want to replace it these methods can be useful.

Services collection 同样也有Contains 和Remove 的方法,记住我们只能给一个给定的type加一个service,如果一个service已经存在,我们想要换它,这些方法会很有用。

Ways of Creating a Service (2) – XML Configuration File

It is also possible to create services using the app.config file. To do this in our simple example we just take out the line:

同样可以使用app.config 来创建服务。 注释掉一下代码

            RootWorkItem.Services.AddNew<MyService>();

Then in an App.Config file we add a ‘services’ section with an ‘add’ element to a CompositeUI config section as below:

在App.Config file 我们加上 如下配置:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <configSections>
    <section name="CompositeUI" type="Microsoft.Practices.CompositeUI.Configuration.SettingsSection, Microsoft.Practices.CompositeUI" allowExeDefinition="MachineToLocalUser" />
  </configSections>
  <CompositeUI>
    <services>
      <add serviceType="Shell.MyService, Shell" instanceType="Shell.MyService, Shell" />
    </services>
  </CompositeUI>
</configuration>

The code for this is available.

In general I’m not a fan of writing code in XML if there are proper C# alternatives. Here the XML is certainly less transparent than the one-line C# equivalent, and as usual debugging becomes more difficult with XML. However, one potential advantage of using the configuration file is that we could in theory change our service at runtime without having to recompile the code.

通常情况下,如果有更合适的C# 的代码可写的情况下,我不是在xml里写代码的粉丝,在此,这些xml代码真是不及一行 C# 代码来的透明,简洁。同样,使用XML,debug也会变得困难。然而一个潜在的优势就是 理论上,我们可以在运行时改变我们的service 而不需要重新编译。

Ways of Creating a Service (3) – the Service Attribute

As mentioned previously, we can create a service simply by decorating our concrete class with the ‘Service’ attribute. We can register the service with a separate interface (as in the section ‘Splitting the Interface from the Implementation’ in part 7) by providing a positional type parameter. We can also make our service one that gets added on demand to the Services collection by adding a named boolean parameter called ‘AddOnDemand’. These attributes are illustrated below:

正如之前提到过的, 我们通过简单的用‘Service’ attribute 装饰我们的类。 我们可以通过一个独立的接口来注册service。我们也能给我们的服务变成AddOnDemand 这种形式 通过加AddOnDemand attribute

[Service(typeof(IMyService), AddOnDemand = true)]
public class MyService : IMyService
{
    public string GetHello()
    {
        return "Hello World";
    }
}

If we declare our service class in this way we have no need to explicitly add it to the Services collection before using it. There’s also no need to explicitly instantiate the class. Just adding the attribute ensures that when the code runs the service will get set up. The code showing this working is available.

如果我们通过这样的方式声明我们的class,我们没必要再使用它时显式地把他加入到Services collection ,同样没必要显式地实例化这个类,只是加入attribute,只是加上attribute来保障当code跑的时候,服务会被设置。

 

Why the Service Attribute is Unusual

The ‘Service’ attribute is in some ways quite different from other attributes we’ve seen used with the CAB. Most CAB attributes only work for objects that are already in a collection associated with a WorkItem. For examples see the discussion about ComponentDependency, ServiceDependency and CreateNew in part 5 of this series of articles. In particular CreateNew will only work on a setter if that setter is in an object that is already in a WorkItem collection. We can’t just put CreateNew in any old class and expect it to work.

‘Service’ attribute  在某些方面与其他CAB里attributes 非常的不一样,大多数情况下CAB attributes  只在对象已经被加入到相关的collection 的时候才起作用, 举例来说,ComponentDependency, ServiceDependency and CreateNew ,尤其是CreateNew  只有在setter的对象已经在WorkItem collection的时候才起作用,我们不能随便把CreateNew  放到一些旧的类里面来期待他们起作用。

 

In contrast the Service attribute will work with ‘any old class’, provided it’s in a module (see part 1 for a discussion of modules). The Service attribute really couldn’t work any other way. The attribute when applied to a class is telling the CAB to add an object of that type to the Services collection of the WorkItem. It wouldn’t make much sense if it only worked if the object was already in a collection of the WorkItem.

相反, Service attribute 会作用于 任何旧的class, 将其放到一个module里。 Service attribute 真的不会起到任何其他坏作用,当把attribute加到一个class里的时候,就会告诉CAB  加一个这种类型的object 到Services collection 里。 最后一句不是最理解 ╮(╯▽╰)╭

 

 

Where the CAB is Looking for the Service Attribute

So how does the CAB find these Service objects and use them? The answer is that when a module loads the CAB uses reflection to find all public classes in the assembly which have the ‘Service’ attribute applied. All of these classes get instantiated and added in to the Services collection of the root WorkItem of the CAB application.

CAB 是怎样这些Service objects  并且使用它们的? 答案是当一个module 被装载的时候,CAB 会使用反射区找寻 所有 assembly 里的public classes,看看谁含有‘Service’ attribute ,所有这些类得到初始化,并且被加到 CAB application 里的Services collection 。

 

Note that the CAB only scans assemblies that are explicitly listed as modules (in ProfileCatalog.xml usually). An assembly won’t get scanned if it’s just referenced from a module project.

注意,CAB 只扫描 ProfileCatalog.xml 里的modules ,如果一个程序集只是被一个module project所引用,他是不会被扫描的。

Drawbacks of the Service Attribute

One problem with this is that we don’t have a lot of control over where the service gets created. Our new service always gets added to the root WorkItem, meaning we can’t create services at a lower level in the WorkItem hierarchy. Another problem is that we have no control over when our service is created: in particular we have no way of ensuring that our services are created in a specific order.

有一个问题:我们对service 在哪里被创建的没有太多的控制权, 我们的新service  总是被加到root WorkItem里,意味着我们不能把service创建在低level的workitem里, 另一个问题是我们对这些服务在何时创建也没有控制权。 尤其是我们没有办法保证我们的服务是通过特殊的order创建。

My personal opinion is that setting up services using the Service attribute can be a little confusing. The services appear magically as if from nowhere. If we explicitly create the service and add it to the appropriate WorkItem we have more control and what we are doing is more transparent.

我的个人选择是通过使用Service attribute 来启动services 会有一点混乱, service会神奇的出现, 如果我们显式地加入service,并把它加到适当的WorkItem 里,我们会有更多的控制力,并且我们所做的也会更有透明度。

 

Ways of Retrieving a Service

There are two main ways of retrieving a service. We have already seen examples of these, but a recap is given below.

有两个方法用于调用service,我们在之前的例子中已经看到过了,现在来个概述:

Ways of Retrieving a Service (1) – Get Method

In the basic example in part 7 we used the Get method of the Services collection to retrieve MyService. For example, the code below is taken from the final example (‘Splitting the Interface from the Implementation’):

    在part7的例子里,我们使用了 Services collection 的 Get 方法来获取MyService,举例来说,以下的例子从final example里获得:
private void UseMyService()
{
    IMyService service = RootWorkItem.Services.Get<IMyService>();
    System.Diagnostics.Debug.WriteLine(service.GetHello());
}

Ways of Retrieving a Service (2) – Dependency Injection

We can also retrieve a service via dependency injection by using the ServiceDependency attribute. We saw some examples of this in part 5.

To set up a service in a class we can decorate a setter of the appropriate type in a class with the ServiceDependency attribute. The class can then use the service:

我们同样可以使用ServiceDependency attribute 凭借 dependency injection来获取service , 我们看过一些例子。为了在一个class里启动一个service ,我们可以ServiceDependency attribute装饰 然后这个class就可以使用这个service了。

public class ServiceClient
{
    private IMyService service;
    [ServiceDependency]
    public IMyService Service
    {
        set
        {
            service = value;
        }
    }
    internal string UseMyService()
    {
        return service.GetHello();
    }
}

As discussed previously, the CAB looks for the ServiceDependency attribute when an object of type ServiceClient is added to one of the WorkItem collections. When that happens the CAB looks for a service of type IMyService in the Services collection of the WorkItem. When it finds one it retrieves it and sets it on the ServiceClient object by calling the setter.

就像之前讨论过的, CAB寻找ServiceDependency 属性 当一个ServiceClient 类型的对象被加入到WorkItem collections的时候,他会去在Services collection 寻找IMyService 类型的service,当它找到后,通过setter将其注入到ServiceClient 对象里。

So to set up this class we need to ensure that an IMyService service has been created, and then we can just create a ServiceClient object in our WorkItem:

为了启动这个class我们得保证 IMyService service 已经被创建, 然后 我们可以只是创建一个ServiceClient  对象在我们的WorkItem里

// Create the service
RootWorkItem.Services.AddNew<MyService, IMyService>();
// Add a ServiceClient object to our Items collection:
// this causes the CAB to inject our service into the ServiceClient
// because it has a setter decorated with ServiceDependency
ServiceClient serviceClient = RootWorkItem.Items.AddNew<ServiceClient>();

Now we can call the service on the ServiceClient object:

现在我们可以再ServiceClient object里调用这个服务了

            System.Diagnostics.Debug.WriteLine(serviceClient.UseMyService());

The code for this example is available.

We can also use the ServiceDependency attribute with constructor injection as discussed in part 6. This is a simple change to the ServiceClient class in the example above:

我们也能使用 加有ServiceDependency attribute 的构造器注入。在以上代码的基础上,做一个简单的改动。

public class ServiceClient
{
    private IMyService service;
    public ServiceClient([ServiceDependency]IMyService service)
    {
        this.service = service;
    }
    internal string UseMyService()
    {
        return service.GetHello();
    }
}

The code for this example is also available.

Finding Services Higher Up the Hierarchy

As already discussed, if the CAB can’t find a service in the Services collection of the current WorkItem it will look in the Services collections of parent WorkItems. We can illustrate this by adding a new WorkItem called ‘testWorkItem’ to our basic example from part 7. We still add our service to the RootWorkItem:

正如我们已经讨论过的一样,如果CAB 不能找到当前的workitem的Services collection ,它会找parent WorkItems 的Services collections. 我们可以通过加一个新的 WorkItem  叫做  ‘testWorkItem’  到我们的例子里来加以说明,我们还是把我们的service 加到RootWorkItem里。

WorkItem testWorkItem = null;
protected override void AfterShellCreated()
{
    testWorkItem = RootWorkItem.WorkItems.AddNew<WorkItem>();
    RootWorkItem.Services.AddNew<MyService, IMyService>();
    UseMyService();
    DisplayWorkItemCollections(RootWorkItem);
}
private void UseMyService()
{
    IMyService service = testWorkItem.Services.Get<IMyService>();
    System.Diagnostics.Debug.WriteLine(service.GetHello());
}

When we come to use the service in UseMyService (immediately above) we try to retrieve it from the testWorkItem. The code still works even though the service isn’t in testWorkItem’s Services collection: the CAB retrieves it from the parent RootWorkItem. Once again the code for this example is available.

当我们使用这个service 的时候,我们首先试图从testWorkItem去找这个服务, 这个代码任然起作用,即使testWorkItem 的Services  collection里没有这个service, CAB会从 parent  RootWorkItem里去寻找。

 

Services Not Found

If the CAB attempts to retrieve a service and can’t find it at all it usually does not throw an exception. It simply returns null. Consider the changes to our basic example from part 7 below:

如果CAB试图取得一个service  但是没能找到它,通常情况下它不会报错, 只是简单的返回一个null。

protected override void AfterShellCreated()
{
    //RootWorkItem.Services.AddNew<MyService, IMyService>();
    UseMyService();
    DisplayWorkItemCollections(RootWorkItem);
}
private void UseMyService()
{
    // There's no IMyService available, so the CAB sets service = null below
    IMyService service = RootWorkItem.Services.Get<IMyService>();
    // We get a NullReferenceException when we try to use the service
    System.Diagnostics.Debug.WriteLine(service.GetHello());
}

Here we have commented out the line that creates the service so it never gets created. As a result the call to ‘Get’ the service returns null, and we get a NullReferenceException when we try to call GetHello.

现在我们注释掉了创建service的代码,它将不会再被创建, 结果使用  ‘Get’ 的时候 它返回了 null,当我们在试图调用GetHello的时候,我们得到一个NullReferenceException错。

This may not be the behaviour we want. It may be better to throw an exception as soon as we know the service does not exist before we attempt to use it. Fortunately the Get method is overloaded to allow us to do this. It can take a boolean argument, EnsureExists, which if set to true throws a ServiceMissingException immediately the service cannot be retrieved:

也许这不是我们想要的表现,也许当我们知道service不存在的时候直接扔一个错出来会更好,幸运的是Get 方法有一个重载,允许我们这么做,可以加入一个参数,来确保存在,如果设置成TRUE的时候,service不存在的时候 ServiceMissingException 会报错

IMyService service = RootWorkItem.Services.Get<IMyService>(true);

The code for this example is available.

Conclusion

This article has shown us how to use services in the CAB in some details. The next two articles will examine commands in the CAB: part 9 will recap the Command design pattern, and part 10 will explain how this is implemented for menus using commands in the CAB.

抱歉!评论已关闭.