web-dev-qa-db-ja.com

c#のWebApi OData(EF)応答からプロパティを除外します

私はC#でWebApiプロジェクトを使用しており(EFコードが最初)、ODataを使用しています。 ID、名前、姓、電子メール、およびパスワードを持つ「ユーザー」モデルがあります。

たとえばコントローラーでは、次のコードがあります。

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

/ odata/Usersを呼び出すと、ID、名前、姓、電子メール、パスワードのすべてのデータが取得されます。

結果からパスワードを除外し、コントローラーでLinqクエリを実行できるようにするにはどうすればよいですか?

9
Martín

私はこの問題に対して巧妙で一時的な解決策を作成しました(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();
}
0
Martín

私はこのトピックに少し遅れていますが、これはあなたを助けるかもしれないと思います。

保存のためにパスワードを暗号化することをお勧めします。 odataアクションを使用してパスワードを設定することを検討しましたか?アクションを使用すると、エンティティを設定するときにパスワードプロパティを無視しながら、エンドユーザーにパスワードを更新するためのクリーンな方法を公開できます。

最初:パスワードプロパティを無視します

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

2番目:odataアクションを追加します

builder.EntityType<UserInfo>().Action("SetPassword").Returns<IHttpActionResult>();

次に、SetPasswordメソッドをUserInfoControllerに追加します。

15
Rick Willis

少し遅れるかもしれませんが、洗練された解決策は、カスタムの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にも適用できます。

8

結果からパスワードを除外し、コントローラーで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);
6
ta.speot.is

次のように、ユーザークラスの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;}
}
6
Maya

あなたがする必要があるのは、元のエンティティの投影されたサブセットを返す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
2
user5292841

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か所に保持できるという利点があります。

1
Paul Hatcher

あなたはすでにこれを試しましたか?

プロパティを更新するだけです。

[EnableQuery]
public async Task<IQueryable<User>> GetUsers()
{
    var users = db.User;

    await users.ForEachAsync(q => q.Password = null);

    return users;
}
1
Igor Quirino

コントローラでドメインモデルを直接クエリしないでください。代わりに、ドメインモデルにマップするQueryModelDTOを作成します。

これらの概念の詳細については、DDDおよびCQRSを参照してください。

0
LastTribunal

他に何もうまくいかなかったので、ここにエレガントな解決策があります。

このように、TableControllerHideSensitiveProperties()拡張メソッドを使用します。

    // 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の基本クラスです。

0
Artemious

必要なデータのみを使用して、DBに新しいビューを作成できます。次に、EntitySetRights.None for Usersテーブルを設定し、作成されたビューに必要な関係を作成します。これで、一般的なodataリクエスト(GET odata/UsersFromView)を実行し、パスワードなしでユーザーデータを取得できます。 Usersテーブルを使用して実行できるリクエストを投稿します。

0
DominGez