我正在使用C#中的WebApi项目(EF代码优先),我正在使用OData。 我有一个“用户”模型,包含Id,Name,LastName,Email和Password。
在控制器例如,我有这个代码:
// GET: odata/Users
[EnableQuery]
public IQueryable<User> GetUsers()
{
return db.Users;
}
如果我打电话给/ odata / Users,我将获得所有数据:Id,Name,LastName,Email和Password。
如何从结果中排除密码,但在控制器中保持可用以进行Linq查询?
答案 0 :(得分:14)
我对这个话题有点迟,但我认为这可能会帮助你。
我认为您需要加密密码以用于存储目的。你有没有看过使用odata动作来设置密码?使用操作可以在设置实体时忽略密码属性,同时仍然向最终用户公开更新密码的干净方式。
首先:忽略密码属性
builder.EntitySet<UserInfo>("UserInfo").EntityType.Ignore(ui => ui.Password);
第二名:添加你的odata动作
builder.EntityType<UserInfo>().Action("SetPassword").Returns<IHttpActionResult>();
然后将SetPassword方法添加到UserInfoController。
答案 1 :(得分:7)
可能有点晚了,但优雅的解决方案是添加自定义的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<User> GetUsers()
{
return db.Users;
}
就是这样!
当然,相同的原则可以应用于自定义QueryableExpandAttribute
。
答案 2 :(得分:6)
在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;}
}
答案 3 :(得分:6)
如何从结果中排除密码,但在控制器中保持可用以进行Linq查询?
忽略它。来自Security Guidance for 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<Employee>("Employees"); employees.EntityType.Ignore(emp => emp.Salary);
答案 4 :(得分:2)
您需要做的是创建一个odata控制器,它返回原始实体的预计子集。
//in WebApi Config Method
config.MapHttpAttributeRoutes();
ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<FullEntity>("FullData");
builder.EntitySet<SubsetEntity>("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());
...然后有一个用于FulLData的两个Odata控制器,一个用于SubsetData(具有不同的安全性),
namespace myapp.Web.OData.Controllers
{
public class SubsetDataController : ODataController
{
private readonly IWarehouseRepository<FullEntity> _fullRepository;
private readonly IUserRepository _userRepository;
public SubsetDataController(
IWarehouseRepository<fullEntity> fullRepository,
IUserRepository userRepository
)
{
_fullRepository = fullRepository;
_userRepository = userRepository;
}
public IQueryable<SubsetEntity> 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
答案 5 :(得分:1)
您已尝试过这个吗?
只需更新房产。
[EnableQuery]
public async Task<IQueryable<User>> GetUsers()
{
var users = db.User;
await users.ForEachAsync(q => q.Password = null);
return users;
}
答案 6 :(得分:1)
我们可以利用ConventionModelBuilder并使用DataContract / DataMember显式启用属性在EdmModel中。
DataContract&amp;数据成员
规则:如果使用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;}
}
这样做的好处是可以将所有映射逻辑保存在项目的一个位置
答案 7 :(得分:0)
我为这个问题做了一个工艺和临时解决方案(不是最好的解决方案,因为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<UserInfo> GetUsers()
{
List<UserInfo> lstUserInfo = new List<UserInfo>();
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();
}
答案 8 :(得分:0)
您可以使用所需的唯一数据在DB中创建新视图。然后为Users表设置EntitySetRights.None并为创建的视图创建必要的关系。 现在,您可以执行常见的odata请求(GET odata / UsersFromView)并获取没有密码的用户数据。您可以使用用户表发布请求。
答案 9 :(得分:0)
没有其他任何对我有用,所以这是一个优雅的解决方案。
使用HideSensitiveProperties()
中的TableController
扩展名方法。
// GET tables/User
public IQueryable<User> GetAllUsers()
{
return Query().HideSensitiveProperties();
}
// GET tables/User/48D68C86-6EA6-4C25-AA33-223FC9A27959
public SingleResult<User> GetUser(string id)
{
return Lookup(id).HideSensitiveProperties();
}
// PATCH tables/User/48D68C86-6EA6-4C25-AA33-223FC9A27959
public Task<User> PatchUser(string id, Delta<User> patch)
{
return UpdateAsync(id, patch).HideSensitivePropertiesForItem();
}
// POST tables/User
public async Task<IHttpActionResult> 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<TData> HideSensitivePropertiesForItem<TData>(this Task<TData> task)
where TData : ModelBase
{
return (await task).HideSensitivePropertiesForItem();
}
public static TData HideSensitivePropertiesForItem<TData>(this TData item)
where TData : ModelBase
{
item.Password = null;
return item;
}
public static SingleResult<TData> HideSensitiveProperties<TData>(this SingleResult<TData> singleResult)
where TData : ModelBase
{
return new SingleResult<TData>(singleResult.Queryable.HideSensitiveProperties());
}
public static IQueryable<TData> HideSensitiveProperties<TData>(this IQueryable<TData> query)
where TData : ModelBase
{
return query.ToList().HideSensitiveProperties().AsQueryable();
}
public static IEnumerable<TData> HideSensitiveProperties<TData>(this IEnumerable<TData> query)
where TData : ModelBase
{
foreach (var item in query)
yield return item.HideSensitivePropertiesForItem();
}
}
此处ModelBase
是所有DTO的基类。
答案 10 :(得分:0)
您不应直接在控制器中查询您的域模型。而是创建映射到域模型的QueryModel DTO。
您可以在DDD和CQRS中阅读有关这些概念的更多信息
答案 11 :(得分:0)
我也迟到了,但我找不到任何好的和干净的方法来实现这一目标。
所以我有这个临时和丑陋的解决方案:
public class MyCustomerClassName
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Key]
public int Id { get; set; }
//normal properties that are exposed to API and read/write to EF
public string Name { get; set; }
public string Adress { get; set; }
//and here, for ONE property named MildlySensitiveInformation :
//the EF property, that is not exposed
[IgnoreDataMember] //says 'dont show that when you serialize (convert to json -> exposed in API)
[Column("MildlySensitiveInformation")] //says 'the REAL column name here is THAT
public string MildlySensitiveInformation_MappedToDB { get; set; } //a 'false' column name, allowing me to expose the real column name in the next property
//the WebApi/Odata property that is exposed
[NotMapped] //Says 'Dont map to EF'
public string MildlySensitiveInformation {
get { return ""; } //says 'never fetch here'
set { MildlySensitiveInformation_MappedToDB = value; } //Says : assign to my real DB properties
}
我暂时不想参加“课堂上的课堂”(DTO)...
我有一个 ODATA 曝光,所以我不想在控制器部分玩:
public class MyCustomerClassNameClassName : ODataController
{
private MyDBContext db = new MyDBContext();
// GET: odata/MyCustomerClassName
[EnableQuery]
public IQueryable<MyCustomerClassName> GetMembresPleinAir()
{
//don't mess here, there is enough 'black magic' going on with $select/$filter/$etc...
var OrigQuery = db.AllMyCustomers;
return OrigQuery;
}
然而,我并不为那个解决方案感到自豪,因为: 1 - 这简直太丑了,我向下一个会触及它的程序员道歉。 2 - 将业务逻辑与“原始”数据库映射混合
所以我想唯一“真实而干净”的解决方案是拥有一个 DTO 类,它允许更好地控制我的数据的读/写方式。
但是,如果我想在我的项目中保持一致,我必须使用“纯类”和“DTO 类”克隆我的 13 个类中的每一个。
答案 12 :(得分:-2)
[EnableQuery]
public IQueryable<User> GetUsers()
{
//Leave password empty
Mapper.CreateMap<User, User>().ForMember(x => x.Password, opt => opt.Ignore());
return db.Users.ToList().Select(u=>Mapper.Map<User>(u)).AsQueryable();
}