从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(); }