65.9K
CodeProject 正在变化。 阅读更多。
Home

提高 WCF 服务质量(第一部分 - 简介和基础知识)

emptyStarIconemptyStarIconemptyStarIconemptyStarIconemptyStarIcon

0/5 (0投票)

2022 年 2 月 28 日

CPOL

3分钟阅读

viewsIcon

4702

提高 WCF 服务质量的基础知识

由于本文篇幅较长,我将其分为几个部分,以提高可读性。

这是第一部分“介绍和基础”。可以通过单击下面的链接找到下一部分。

  1. 验证
  2. 身份验证/授权
  3. 日志记录

引言

本文基于我在职业生涯中所做的一个重构项目。由于安全策略的变更,我被要求重构现有的 WCF 服务,使其更安全且可追踪。我决定将授权、身份验证、日志记录和验证概念引入到现有的服务架构中,而不是重写整个服务代码。

最初的想法是重写所有服务,但由于我们目前使用了数百个服务方法,这个想法会带来压倒性的开发时间。相反,我决定使用面向切面编程 (AOP)来减少开发工作。

背景

有几个不错的 AOP 库可用于 C#,例如 CastleAspectSharpAspect.NETPostSharp。 由于 PostSharp 易于使用、文档良好以及社区上的示例数量众多,因此选择 PostSharp 用于此项目。

Microsoft SQL Server 用于存储数据,Entity Framework 用于数据访问。 尽管 .NET Core 可能是一种更现代的方法,但由于我们试图减少开发工作,因此使用 .NET Framework 来实现向后兼容。

Using the Code

作为一种常见的做法,大多数开发人员编写 WCF 服务如下

[ServiceContract]
public interface IExampleService
{
    [OperationContract]
    int CreatePlayer(string name, DateTime dateofBirth, int? height, int? weight, string club);
    [OperationContract]
    Player GetPlayerById(int id);
    [OperationContract]
    List<Player> GetAllPlayers();
    [OperationContract]
    List<Player> GetClubPlayers(string club);
}
public class ExampleService : IExampleService
{
    public int CreatePlayer(string name, DateTime dateofBirth, int? height, 
                            int? weight, string club)
    {
        return PlayerRepository.CreateNewPlayer(name, dateofBirth, height, weight, club);
    }
    public List<Player> GetAllPlayers()
    {
        return PlayerRepository.GetAllPlayers();
    }
    public List<Player> GetClubPlayers(string club)
    {
        return PlayerRepository.GetClubPlayers(club);
    }
    public Player GetPlayerById(int id)
    {
        return PlayerRepository.GetPlayerById(id);
    }
}

我们的第一个操作是将多个输入参数更改为一个包含这些参数的类,该类作为数据协定,并且作为命名约定,由于它们是请求的一部分,因此将它们命名为方法名称 + Request。

[DataContract]
public class CreatePlayerRequest
{
    [DataMember]
    public string Name { get; set; }
    [DataMember]
    public DateTime DateOfBirth { get; set; }
    [DataMember]
    public int? Height { get; set; }
    [DataMember]
    public int? Weight { get; set; }
    [DataMember]
    public string Club { get; set; }
}
[DataContract]
public class GetClubPlayersRequest
{
    [DataMember]
    public string Club { get; set;}
}
[DataContract]
public class GetPlayerByIdRequest
{
    [DataMember]
    public int PlayerId { get; set; }
}

然后为每个特定的服务方法创建响应对象,同样使用命名约定:方法名称 + Response。

[DataContract]
public CreatePlayerResponse
{
    [DataMember]
    public int PlayerId { get; set; }
}
[DataContract]
public GetAllPlayerResponse
{
    [DataMember]
    public List<Player> PlayerList { get; set; }
}
[DataContract]
public GetClubPlayersResponse
{
    [DataMember]
    public List<Player> PlayerList { get; set; }
}
[DataContract]
public GetPlayerByIdResponse
{
    [DataMember]
    public Player Player { get; set; }
}

当然,可以使用一个 Response 对象而不是 GetAllPlayerResponseGetClubPlayersResponse 对象。

下一步是创建一个名为“WCFServices.Shared”的新类库项目,并将其引用到我们现有的服务。创建并引用此项目后,让我们分别创建这两个类。

[DataContract]
public class BaseRequest
{
    [DataMember]
    public string Username { get; set; }
    [DataMember]
    public string Password { get; set; }
}
[DataContract]
public class BaseResponse
{
    [DataMember]
    public bool IsException { get; set; }
    [DataMember]
    public bool IsSuccess { get; set; }
    [DataMember]
    public string[] Messages { get; set; }
}

新创建的 BaseRequest 类将是我们所有请求对象的基类,并将保存身份验证和授权所需的必要信息。 采用类似的方法,BaseResponse 类将是我们所有响应对象的基类,并将通过三个属性保存所有方法的响应状态信息

  • IsException:如果在方法调用期间发生异常,则将设置为 true。 如果发生异常,则应将 IsSuccess 属性设置为 false,并且 Messages 属性应返回异常的 Message 属性。
  • IsSuccess:如果该方法以成功的方式完成,则将设置为 true。 如果将其设置为 true,则应将 IsException 属性设置为 false,并将 Messages 属性设置为常量消息。
  • Messages:此属性可以返回一行或多行消息,告知客户端有关请求的状态。 除了有关异常(如果发生)或成功消息的详细信息之外,此属性还可以保存验证失败的值。

创建这些类后,应按如下方式更新服务中的所有请求和响应类

[DataContract]
public class CreatePlayerRequest : BaseRequest
{
    [DataMember]
    public string Name { get; set; }
    [DataMember]
    public DateTime DateOfBirth { get; set; }
    [DataMember]
    public int? Height { get; set; }
    [DataMember]
    public int? Weight { get; set; }
    [DataMember]
    public string Club { get; set; }
}
[DataContract]
public class GetClubPlayersRequest : BaseRequest
{
    [DataMember]
    public string Club { get; set;}
}
[DataContract]
public class GetPlayerByIdRequest : BaseRequest
{
    [DataMember]
    public int PlayerId { get; set; }
}
[DataContract]
public CreatePlayerResponse : BaseResponse
{
    [DataMember]
    public int PlayerId { get; set; }
}
[DataContract]
public GetAllPlayersResponse : BaseResponse
{
    [DataMember]
    public List<Player> PlayerList { get; set; }
}
[DataContract]
public GetClubPlayersResponse : BaseResponse
{
    [DataMember]
    public List<Player> PlayerList { get; set; }
}
[DataContract]
public GetPlayerByIdResponse : BaseResponse
{
    [DataMember]
    public Player Player { get; set; }
}

我们的服务和接口将如下所示

[ServiceContract]
public interface IExampleService
{
    [OperationContract]
    CreatePlayerResponse CreatePlayer(CreatePlayerRequest request);
    [OperationContract]
    GetPlayerByIdResponse GetPlayerById(GetPlayerByIdRequest request);
    [OperationContract]
    GetAllPlayersResponse GetAllPlayers(BaseRequest request);
    [OperationContract]
    GetClubPlayersResponse GetClubPlayers(GetClubPlayersRequest request);
}
public class ExampleService : IExampleService
{
    public CreatePlayerResponse CreatePlayer(CreatePlayerRequest request)
    {
        try
        {
            return new CreatePlayerResponse
            {
                PlayerId = PlayerRepository.CreateNewPlayer(request.Name, 
                  request.DateOfBirth, request.Height, request.Weight, request.Club),
                IsException = false,
                IsSuccess = false,
                Messages = new string[] { "Operation successful" }
            };
        }
        catch(Exception ex)
        {
            return new CreatePlayerResponse
            {
                IsException = true,
                IsSuccess = false,
                Messages = new string[] { ex.Message; }
            };
        }
    }
    public GetAllPlayersResponse GetAllPlayers(BaseRequest request)
    {
        try
        {
            return new GetAllPlayersResponse
            {
                PlayerList = PlayerRepository.GetAllPlayers(),
                IsException = false,
                IsSuccess = false,
                Messages = new string[] { "Operation successful" }
            };
        }
        catch(Exception ex)
        {
            return new GetAllPlayersResponse
            {
                IsException = true,
                IsSuccess = false,
                Messages = new string[] { ex.Message; }
            };
        }
    }
    // I think the first two methods give enough clue about the transformation.
    :
    :
}

这是我们系列的第一部分,解释了对我们的服务结构进行一些更新并为我们的下一章做准备的基本方法。

您可以在此处阅读下一部分(验证)。

历史

  • 2022 年 2 月 28 日:初始版本
© . All rights reserved.