2017-02-23 76 views
1

我正在构建WebApi2项目以公开一些RESTful服务。假设我有以下模型对象。未使用EF的WebApi字段过滤

public class Person 
{ 
    public string Name { get; set; } 

    public DateTime? Birthdate { get; set; } 

    public int Status { get; set; } 

    public List<Account> Accounts { get; set; } 
} 

public class Account 
{ 
    public decimal Amount { get; set; } 

    public string Code { get; set; } 

    public DateTime Expiry { get; set; } 
} 

在我的服务中,我必须去2个不同的系统检索个人的数据和人的帐户信息。显然,服务的实现看起来像

[HttpGet] 
    [Route("Person/{id:int}")] 
    public IHttpActionResult Get(string id) 
    { 
     var person = new Person(); 
     person = GetPersonFromSystemA(id); 

     if (person.Status == 2) 
     { 
      person.Accounts = GetPersonAccountsFromSystemB(id); 
     } 

     return this.Ok(person); 
    } 

我不能在这种情况下使用EF可言,所以的OData是非常棘手的。

我有一些要求,我需要提供过滤功能的服务客户端。客户可以决定返回哪些对象字段,这也意味着如果客户端不想包含该人员的账户信息,我应该跳过第二次调用系统B以避免整个子对象。

我做了一些快速搜索,但我找不到一些类似的解决方案。我知道我可以实现我自己的过滤语法,并让所有自定义代码使用过滤(通过有很多if/else)。

我在寻找更优雅的解决方案的一些想法。

回答

0

构建OData服务不需要实体框架。如果您不使用OData,您可能必须实现您自己的IQueryable,这是OData开箱即用的功能。

一些示例代码。

模型类增加了一些特性

public class Person 
{ 
    [Key] 
    public String Id { get; set; } 
    [Required] 
    public string Name { get; set; } 
    public DateTime? Birthdate { get; set; } 
    public int Status { get; set; } 
    public List<Account> Accounts { get; set; } 
} 

public class Account 
{ 
    [Key] 
    public String Id { get; set; } 
    [Required] 
    public decimal Amount { get; set; } 
    public string Code { get; set; } 
    public DateTime Expiry { get; set; } 
} 

WebApiConfig.cs

public static class WebApiConfig 
{ 
    public static void Register(HttpConfiguration config) 
    { 
     config.MapODataServiceRoute("odata", null, GetEdmModel(), new DefaultODataBatchHandler(GlobalConfiguration.DefaultServer)); 
     config.EnsureInitialized(); 
    } 

    private static IEdmModel GetEdmModel() 
    { 
     ODataConventionModelBuilder builder = new ODataConventionModelBuilder(); 
     builder.Namespace = "YourNamespace"; 
     builder.ContainerName = "DefaultContainer"; 
     builder.EntitySet<Person>("People"); 
     builder.EntitySet<Account>("Accounts"); 
     var edmModel = builder.GetEdmModel(); 
     return edmModel; 
    } 
} 

控制方法

[EnableQuery] 
public class PeopleController : ODataController 
{ 
    public IHttpActionResult Get() 
    { 
     return Ok(SomeDataSource.Instance.People.AsQueryable()); 
    } 
} 

您将需要包括Microsoft.AspNet.OData Nuget包。

有关更多指导,请参阅以下内容。它使用内存数据源,但不管怎样,概念都是一样的。

http://www.odata.org/blog/how-to-use-web-api-odata-to-build-an-odata-v4-service-without-entity-framework/

+0

感谢您的评论,您的实现假设我必须首先获取所有数据,无论它们是否有用,然后应用过滤。如果EF/OData实现使用延迟加载,则根本不会加载过滤出的日期。我也尝试实现类似的功能来加载我需要的数据,因为调用外部系统很昂贵。 – hardywang

0

当构建你会经常要筛选您回应并获得唯一的某些字段的web API。你可以用很多方式做到这一点,其中之一如上所述。另一种方法,你可以通过你的web api使用数据整形。

如果你有一个控制器动作这样:

public IHttpActionResult Get(string fields="all") 
{ 
    try 
    { 
     var results = _tripRepository.Get(); 
     if (results == null) 
      return NotFound(); 
     // Getting the fields is an expensive operation, so the default is all, 
     // in which case we will just return the results 
     if (!string.Equals(fields, "all", StringComparison.OrdinalIgnoreCase)) 
     { 
      var shapedResults = results.Select(x => GetShapedObject(x, fields)); 
      return Ok(shapedResults); 
     } 
     return Ok(results); 
    } 
    catch (Exception) 
    { 
     return InternalServerError(); 
    } 
} 

然后你GetShapedData方法可以做过滤这样:

public object GetShapedObject<TParameter>(TParameter entity, string fields) 
{ 
    if (string.IsNullOrEmpty(fields)) 
     return entity; 
    Regex regex = new Regex(@"[^,()]+(\([^()]*\))?"); 
    var requestedFields = regex.Matches(fields).Cast<Match>().Select(m => m.Value).Distinct(); 
    ExpandoObject expando = new ExpandoObject(); 

    foreach (var field in requestedFields) 
    { 
     if (field.Contains("(")) 
     { 
      var navField = field.Substring(0, field.IndexOf('(')); 

      IList navFieldValue = entity.GetType() 
            ?.GetProperty(navField, BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.Public) 
            ?.GetValue(entity, null) as IList; 
      var regexMatch = Regex.Matches(field, @"\((.+?)\)"); 
      if (regexMatch?.Count > 0) 
      { 
       var propertiesString = regexMatch[0].Value?.Replace("(", string.Empty).Replace(")", string.Empty); 
       if (!string.IsNullOrEmpty(propertiesString)) 
       { 
        string[] navigationObjectProperties = propertiesString.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); 

        List<object> list = new List<object>(); 
        foreach (var item in navFieldValue) 
        { 
         list.Add(GetShapedObject(item, navigationObjectProperties)); 
        } 

        ((IDictionary<string, object>)expando).Add(navField, list); 
       } 
      } 
     } 
     else 
     { 
      var value = entity.GetType() 
          ?.GetProperty(field, BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.Public) 
          ?.GetValue(entity, null); 
      ((IDictionary<string, object>)expando).Add(field, value); 
     } 
    } 

    return expando; 
} 

检查我的博客进行了详细的职位:https://jinishbhardwaj.wordpress.com/2016/12/03/web-api-supporting-data-shaping/

+0

这是一个很好的解决方案,我会研究它的结果。谢谢。 – hardywang