私はC#でWebApiプロジェクトを使用しており(EFコードが最初)、ODataを使用しています。 ID、名前、姓、電子メール、およびパスワードを持つ「ユーザー」モデルがあります。
たとえばコントローラーでは、次のコードがあります。
// GET: odata/Users
[EnableQuery]
public IQueryable<User> GetUsers()
{
return db.Users;
}
/ odata/Usersを呼び出すと、ID、名前、姓、電子メール、パスワードのすべてのデータが取得されます。
結果からパスワードを除外し、コントローラーでLinqクエリを実行できるようにするにはどうすればよいですか?
私はこの問題に対して巧妙で一時的な解決策を作成しました(UserInfoはエンティティタイプではなく、$ selectまたは$ expandをサポートしていないため、最善の解決策ではありません)。必要なプロパティ(ユーザーを除く)だけを使用して、UserInfoという新しいモデルを作成しました。
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();
}
私はこのトピックに少し遅れていますが、これはあなたを助けるかもしれないと思います。
保存のためにパスワードを暗号化することをお勧めします。 odataアクションを使用してパスワードを設定することを検討しましたか?アクションを使用すると、エンティティを設定するときにパスワードプロパティを無視しながら、エンドユーザーにパスワードを更新するためのクリーンな方法を公開できます。
最初:パスワードプロパティを無視します
builder.EntitySet<UserInfo>("UserInfo").EntityType.Ignore(ui => ui.Password);
2番目:odataアクションを追加します
builder.EntityType<UserInfo>().Action("SetPassword").Returns<IHttpActionResult>();
次に、SetPasswordメソッドをUserInfoControllerに追加します。
少し遅れるかもしれませんが、洗練された解決策は、カスタムの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
にも適用できます。
結果からパスワードを除外し、コントローラーでLinqクエリを実行できるようにするにはどうすればよいですか?
それを無視します。 From ASP.NET Web API 2 ODataのセキュリティガイダンス :
プロパティをEDMから除外する方法は2つあります。モデルクラスのプロパティに[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);
次のように、ユーザークラスの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;}
}
あなたがする必要があるのは、元のエンティティの投影されたサブセットを返す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());
...次に、2つのOdataコントローラーがあります。1つはFulLData用、もう1つは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
ConventionModelBuilder を利用し、DataContract/DataMemberを使用して、プロパティをEdmModelに明示的に有効にすることができます。
DataContract&DataMember
ルール:DataContractまたはDataMemberを使用する場合、[DataMember]属性を持つプロパティのみがEdmモデルに追加されます。
[NotMapped]属性を使用していないため、これはEntityFrameworkモデルに影響を与えないことに注意してください(どちらのモデルでも使用したくない場合を除く)
[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;}
}
これには、プロジェクト内のすべてのマッピングロジックを1か所に保持できるという利点があります。
あなたはすでにこれを試しましたか?
プロパティを更新するだけです。
[EnableQuery]
public async Task<IQueryable<User>> GetUsers()
{
var users = db.User;
await users.ForEachAsync(q => q.Password = null);
return users;
}
コントローラでドメインモデルを直接クエリしないでください。代わりに、ドメインモデルにマップするQueryModelDTOを作成します。
これらの概念の詳細については、DDDおよびCQRSを参照してください。
他に何もうまくいかなかったので、ここにエレガントな解決策があります。
このように、TableController
でHideSensitiveProperties()
拡張メソッドを使用します。
// 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の基本クラスです。
必要なデータのみを使用して、DBに新しいビューを作成できます。次に、EntitySetRights.None for Usersテーブルを設定し、作成されたビューに必要な関係を作成します。これで、一般的なodataリクエスト(GET odata/UsersFromView)を実行し、パスワードなしでユーザーデータを取得できます。 Usersテーブルを使用して実行できるリクエストを投稿します。