从c#中的WebApi OData(EF)响应中排除属性

我正在使用C#的WebApi项目(EF代码优先),我正在使用OData。 我有一个“用户”模型,包含Id,Name,LastName,Email和Password。

在控制器例如我有这个代码:

// GET: odata/Users [EnableQuery] public IQueryable GetUsers() { return db.Users; } 

如果我调用/ odata / Users,我将获得所有数据:Id,Name,LastName,Email和Password。

如何从结果中排除密码,但在控制器中保持可用以进行Linq查询?

我对这个话题有点迟,但我认为这可能对你有帮助。

我假设您需要加密密码以用于存储目的。 你有没有看过使用odata动作来设置密码? 使用操作可让您在设置实体时忽略密码属性,同时仍向最终用户公开干净的方式来更新密码。

第一:忽略密码属性

 builder.EntitySet("UserInfo").EntityType.Ignore(ui => ui.Password); 

第二:添加你的odata动作

 builder.EntityType().Action("SetPassword").Returns(); 

然后将SetPassword方法添加到UserInfoController。

在User Class的Password属性中添加[NotMapped]属性,如下所示:

 public class User { public int Id { get; set; } public string Name { get; set; } public string Email { get; set; } public string LastName {get; set; } [NotMapped] public string Password {get; set;} } 

它可能有点晚,但优雅的解决方案是添加自定义QueryableSelectAttribute,然后只列出要在服务端选择的字段。 在你的情况下,它看起来像这样:

 public class QueryableSelectAttribute : ActionFilterAttribute { private const string ODataSelectOption = "$select="; private string selectValue; public QueryableSelectAttribute(string select) { this.selectValue = select; } public override void OnActionExecuting(HttpActionContext actionContext) { base.OnActionExecuting(actionContext); var request = actionContext.Request; var query = request.RequestUri.Query.Substring(1); var parts = query.Split('&').ToList(); for (int i = 0; i < parts.Count; i++) { string segment = parts[i]; if (segment.StartsWith(ODataSelectOption, StringComparison.Ordinal)) { parts.Remove(segment); break; } } parts.Add(ODataSelectOption + this.selectValue); var modifiedRequestUri = new UriBuilder(request.RequestUri); modifiedRequestUri.Query = string.Join("&", parts.Where(p => p.Length > 0)); request.RequestUri = modifiedRequestUri.Uri; base.OnActionExecuting(actionContext); } } 

在控制器中,您只需添加具有所需属性的属性:

 [EnableQuery] [QueryableSelect("Name,LastName,Email")] public IQueryable GetUsers() { return db.Users; } 

就是这样!

当然,相同的原则可以应用于自定义QueryableExpandAttribute

如何从结果中排除密码,但在控制器中保持可用以进行Linq查询?

忽略它。 从ASP.NET Web API 2 OData的安全指南 :

有两种方法可以从EDM中排除财产。 您可以在模型类中的属性上设置[IgnoreDataMember]属性:

 public class Employee { public string Name { get; set; } public string Title { get; set; } [IgnoreDataMember] public decimal Salary { get; set; } // Not visible in the EDM } 

您还可以通过编程方式从EDM中删除该属性:

 var employees = modelBuilder.EntitySet("Employees"); employees.EntityType.Ignore(emp => emp.Salary); 

你需要做的是创建一个odata控制器,它返回原始实体的一个预计子集。

 //in WebApi Config Method config.MapHttpAttributeRoutes(); ODataConventionModelBuilder builder = new ODataConventionModelBuilder(); builder.EntitySet("FullData"); builder.EntitySet("SubsetData"); config.Routes.MapODataServiceRoute("odata", "odata", builder.GetEdmModel()); config.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{controller}/{action}/{id}", defaults: new { id = RouteParameter.Optional, action = "GET" } ); SetupJsonFormatters(); config.Filters.Add(new UncaughtErrorHandlingFilterAttribute()); 

…然后有两个Odata控制器一个用于FulLData,一个用于SubsetData(具有不同的安全性),

 namespace myapp.Web.OData.Controllers { public class SubsetDataController : ODataController { private readonly IWarehouseRepository _fullRepository; private readonly IUserRepository _userRepository; public SubsetDataController( IWarehouseRepository fullRepository, IUserRepository userRepository ) { _fullRepository = fullRepository; _userRepository = userRepository; } public IQueryable Get() { Object webHostHttpRequestContext = Request.Properties["MS_RequestContext"]; System.Security.Claims.ClaimsPrincipal principal = (System.Security.Claims.ClaimsPrincipal) webHostHttpRequestContext.GetType() .GetProperty("Principal") .GetValue(webHostHttpRequestContext, null); if (!principal.Identity.IsAuthenticated) throw new Exception("user is not authenticated cannot perform OData query"); //do security in here //irrelevant but this just allows use of data by Word and Excel. if (Request.Headers.Accept.Count == 0) Request.Headers.Add("Accept", "application/atom+xml"); return _fullRepository.Query().Select( b=> new SubsetDataListEntity { Id = b.Id, bitofData = b.bitofData } } //end of query } //end of class 

你已经试过了吗?

只需更新房产。

 [EnableQuery] public async Task> GetUsers() { var users = db.User; await users.ForEachAsync(q => q.Password = null); return users; } 

我们可以利用ConventionModelBuilder并使用DataContract / DataMember显式启用属性在EdmModel中。

DataContract和DataMember

规则:如果使用DataContract或DataMember,则只有具有[DataMember]属性的属性才会添加到Edm模型中。

请注意,这不会影响EntityFramework模型,因为我们没有使用[NotMapped]属性(除非您不想在任何一个模型中使用它)

 [DataContract] public class User { [DataMember] public int Id { get; set; } [DataMember] public string Name { get; set; } [DataMember] public string Email { get; set; } [DataMember] public string LastName {get; set; } // NB Password won't be in EdmModel but still available to EF public string Password {get; set;} } 

这样做的好处是可以将所有映射逻辑保存在项目的一个位置

我为这个问题做了一个工艺和临时解决方案(不是最好的解决方案,因为UserInfo不是实体类型,不支持$ select或$ expand)。 我创建了一个名为UserInfo的新模型,只需要我需要的属性(User的一部分):

 public class UserInfo { public int Id { get; set; } public string Name { get; set; } public string Email { get; set; } } 

然后我改变了控制器中的方法:

 // GET: odata/Users [EnableQuery] public IQueryable GetUsers() { List lstUserInfo = new List(); foreach(User usr in db.Users) { UserInfo userInfo = new UserInfo(); userInfo.Id = usr.Id; userInfo.Name = usr.Name; userInfo.Email = usr.Email; lstUserInfo.Add(userInfo); } return lstUserInfo.AsQueryable(); } 

您可以使用所需的唯一数据在DB中创建新视图。 然后为Users表设置EntitySetRights.None,并为创建的视图创建必要的关系。 现在,您可以执行常见的odata请求(GET odata / UsersFromView)并获取没有密码的用户数据。 您可以使用Users表发布请求。

没有其他任何对我有用,所以这是一个优雅的解决方案。

像这样使用HideSensitiveProperties()扩展方法。

  // GET tables/User public IQueryable GetAllUsers() { return Query().HideSensitiveProperties(); } // GET tables/User/48D68C86-6EA6-4C25-AA33-223FC9A27959 public SingleResult GetUser(string id) { return Lookup(id).HideSensitiveProperties(); } // PATCH tables/User/48D68C86-6EA6-4C25-AA33-223FC9A27959 public Task PatchUser(string id, Delta patch) { return UpdateAsync(id, patch).HideSensitivePropertiesForItem(); } // POST tables/User public async Task PostUser(User item) { User current = await InsertAsync(item); current.HideSensitivePropertiesForItem(); return CreatedAtRoute("Tables", new { id = current.Id }, current); } // DELETE tables/User/48D68C86-6EA6-4C25-AA33-223FC9A27959 public Task DeleteUser(string id) { return DeleteAsync(id); } 

虽然这不会从响应中删除属性名称,但会将其值设置为null

 public static class HideSensitivePropertiesExtensions { public static async Task HideSensitivePropertiesForItem(this Task task) where TData : ModelBase { return (await task).HideSensitivePropertiesForItem(); } public static TData HideSensitivePropertiesForItem(this TData item) where TData : ModelBase { item.Password = null; return item; } public static SingleResult HideSensitiveProperties(this SingleResult singleResult) where TData : ModelBase { return new SingleResult(singleResult.Queryable.HideSensitiveProperties()); } public static IQueryable HideSensitiveProperties(this IQueryable query) where TData : ModelBase { return query.ToList().HideSensitiveProperties().AsQueryable(); } public static IEnumerable HideSensitiveProperties(this IEnumerable query) where TData : ModelBase { foreach (var item in query) yield return item.HideSensitivePropertiesForItem(); } } 

这里ModelBase是所有DTO的基类。

您不应直接在控制器中查询您的域模型。 而是创建一个映射到域模型的QueryModel DTO。

您可以在DDD和CQRS中阅读有关这些概念的更多信息

使用Automapper

 [EnableQuery] public IQueryable GetUsers() { //Leave password empty Mapper.CreateMap().ForMember(x => x.Password, opt => opt.Ignore()); return db.Users.ToList().Select(u=>Mapper.Map(u)).AsQueryable(); }