web api 2 学习笔记 (Odata basic)

开发时不同状况下的方案 :

1. 权限管理 , 在expand时要确保被expand的资源是无需访问权限的,因为expand在后台很难做where, 所以针对有权限要求的资源就不允许expand而改用另一些接口,比如 members(5)/orders 而不是members + expand orders  

1.1 针对不同权限能expand的column不同也是非常难搞的,基本上就是要在controller 通过分析ODataQueryOptions.selectExpand 才能处理. 

 

2.级联删除,最好是通过sql来做级联删除,除非我们需要做历史垃圾桶记入才用EF实现,EF的级联是需要加载所有的子孙资源出来,才能透过删除parent自能的删除子层,性能不佳. 

 

3.post model validation & logic fill , 简单就是用自带validation的,困难的就开方法, logic fill 也是开方法

 

4.column 不允许修改就透过开方法拦截saveChange set isModified = false 来实现。

 

5.并发问题,基本上都是采用乐观并发,就是说大家都可以随时改,但是最终save的时候可能会error。

   主要就是看新value是依据哪些资源来确定的,我们就必须确保那些资源都是没人修改过的。(大部分情况下都是依据资源本身,所以EF也只支持到那,但是实际情况下可能会更复杂) 

   insert也是会有并发问题的,而且并不一定是unique。只要一个action依赖其它资源,在执行action时就要确保依赖的资源是没被修改过的。 

 

返回 HttpResponseMessage

public async Task<HttpResponseMessage> post(Product product)
{  
    db.products.Add(product);
    await db.SaveChangesAsync();
    var response = Request.CreateResponse<Product>(HttpStatusCode.OK, product);
    response.Headers.Add("custome", "haha");            
    return response; 
}

 

Put [ODataRoute("({id})")]public async Task<IHttpActionResult> put(int id, Color color, ODataQueryOptions<Color> options){

复制代码
if (options.IfMatch != null && !(options.IfMatch.ApplyTo(db.colors.Where(c => c.id == id)) as IQueryable<Color>).Any())
    {
        //concurrency
    }
    else
    {

    }
    color.id = id;
    db.Entry(color).State = EntityState.Modified;
db.Entry(companySQL).CurrentValues.SetValues(companyDTO);
db.Entry(companySQL).OriginalValues["rowVersion"] = companyDTO.rowVersion;
db.Entry(color).Property(c => c.Products_id).IsModified = false;
await db.SaveChangesAsync();
this.detachedAllEntity();
return Ok(color);
}
复制代码

前台的request 要加header "if-Match": resource["@odata.etag"]

 

Get navigationProps

复制代码
[ODataRoute("({id})/product")]
[ODataRoute("({id})/sizes")]
public async Task<IHttpActionResult> getProp(int id)
{
    //future: 没有实现动态的原因是因为目前没办法做好返回的类型,简单返回object的话,odata to json 时没办法匹配到EDM
    var navigationPropName = this.Url.Request.RequestUri.Segments.Last();
    switch (navigationPropName)
    {
        case "sizes":
            return Ok(db.colors.Where(c => c.id == id).SelectMany(c => c.sizes));
        case "product":
            return Ok((await db.colors.Include(c => c.product).FirstOrDefaultAsync(c => c.id == id)).product);
        default:
            return BadRequest();
    }
}
复制代码

 

Single result 

public SingleResult<LoginPerson> getLoginPersonByUserObject()
{
    if (!(User is LoginPerson)) throw new AjaxEx("noCookie");
    LoginPerson LoginPerson = User as LoginPerson;
    IQueryable<LoginPerson> query = db.LoginPersons.Include(l => l.roles).Include(l => l.accountBindings).Where(l => l.id == LoginPerson.id);
    return SingleResult.Create<LoginPerson>(query);
}

 

Get 

[ODataRoute("")]
[EnableQuery(AllowedQueryOptions = WebApiConfig.ALLOW_QUERY | AllowedQueryOptions.Expand, MaxExpansionDepth = 5)]
public IQueryable<Employer> get()
{
    return db.Employers;
}

 

Get + 权限管理

refer : http://www.asp.net/web-api/overview/odata-support-in-aspnet-web-api/supporting-odata-query-options

动态 EnableQuery 

不是用 options.ApplyTo , 因为他会破坏 $expand 和 $select,以后可能可以. refer : http://stackoverflow.com/questions/18879779/select-and-expand-break-odataqueryoptions-how-to-fix

复制代码
[ODataRoute("")]
[EnableQuery] //开放到完
public IQueryable<Employer> get(ODataQueryOptions<Employer> options)
{
    //动态验证          
    //base on 不同权限设置 ODataValidationSettings
    var settings = new ODataValidationSettings()
    { 
        AllowedQueryOptions = WebApiConfig.ALLOW_QUERY
    };
    //一定要用 catch 
    try
    {
        options.Validate(settings);
    }
    catch (ODataException ex)
    {
        throw new AjaxEx(ex.Message); 
    }             
    return db.Employers; 
}
复制代码

 

many to many RPC

复制代码
public class UpdateMMCategorysBody
{
    public List<int> removeIds { get; set; }
    public List<int> addIds { get; set; }
}
[ODataRoute("({id})/RPC.updateMMCategorys")]
public async Task<IHttpActionResult> updateMMCategorys(int id, UpdateMMCategorysBody data)
{
    var product = await db.products.Include(p => p.categorys).FirstOrDefaultAsync(p => p.id == id);
    if (product == null) throw new AjaxEx("productNotFound");
    for (int i = product.categorys.Count - 1; i >= 0; i--)
    {
        var category = product.categorys[i];
        if (data.removeIds.Contains(category.id))
        {
            product.categorys.Remove(category);
        }
    }
    var categoryForAdds = await db.categorys.Where(c => data.addIds.Contains(c.id)).ToListAsync();
    if (categoryForAdds.Count != data.addIds.Count) throw new AjaxEx("categoryNotFound");
    categoryForAdds.ForEach(c =>
    {
        product.categorys.Add(c);
    });
    await db.SaveChangesAsync();
    return Ok("ok");
}
复制代码

 

BaseController : 

复制代码
public class BaseController : ODataController
{
    protected DB db = DB.Sharing.getDb();
    protected override void Dispose(bool disposing)
    {
        DB.Sharing.disposeDb();
        DB.Sharing.disposeTansaction();
        base.Dispose(disposing);
    }
    protected void detachedAllEntity()
    {
        var manager = ((IObjectContextAdapter)this.db).ObjectContext.ObjectStateManager;
        var objectStateEntries = manager.GetObjectStateEntries(EntityState.Added | EntityState.Deleted | EntityState.Modified | EntityState.Unchanged);
        foreach (var objectStateEntry in objectStateEntries)
        {
            db.Entry(objectStateEntry.Entity).State = EntityState.Detached;
        }
    }
}
复制代码

 

POST with model validation 

refer :http://www.asp.net/web-api/overview/formats-and-model-binding/model-validation-in-aspnet-web-api

refer : https://msdn.microsoft.com/en-us/library/system.componentmodel.dataannotations.aspx

复制代码
[ODataRoute("")]
public async Task<IHttpActionResult> post(Employer employer)
{
    if (ModelState.IsValid)
    {
        db.Employers.Add(employer);
        await db.SaveChangesAsync();
        return Ok(employer);
    }
    else
    {
        return BadRequest(ModelState);
    }           
}
复制代码

 

获取完整的 restful hateoas response 

note :  builder.EntitySet<Class>("Classs"); <-每一个class都要写,不然expand的话会error哦


$http({ url:
"//localhost:11207/api/Employers", headers : { Accept: "application/json;odata.metadata=full" }, method : "GET" });

 

常用的 url 写法 

/api/Products?$filter=colors/any(c:c/id gt 0); 找出有color的product

(本来应该用$count的而不是 gt 0, but refer http://forums.asp.net/t/2070250.aspx Web+API+2+OData+v4+filter+Count+of+a+Collection)

/api/Products?$filter=not(colors/any(c:c/id gt 0));  找出没有color的product

/api/Colors?$filter=type eq EFDB.ColorType'a';   filter by Enum

/api/comments?$filter=user/EFDB.Salesman/salesmanColumn eq 'abc'  filter 抽象类和属性

/api/EmployerTicketSlots?$filter=EFDB.EmployerTicketSlot/id gt 0 ,  filter抽象类 本来应该不是用 gt 0,but 暂时没找到正规写法

for expand : /api/Singles?$expand=abstracts($expand=EFDB.AA/childs,EFDB.BB/childs)  expand 派生类和子层, 可以同时多种派生类哦

/api/Products?createDt gt 2015-10-10T08:00:00.0000000+08:00  filter datetimeoffset,用UTC也可以, js : new Date().toJSON();

/api/Products?$filter=contains(code,'mk100'))   filter like

/api/Products?$expand=colors($filter=ID eq 5;$expand=sizes;$orderby=ID desc;$select=name); nested expand 

/api/Products?$expand=* 全部都expand, 只是 1 层而已噢

/api/people?$expand=upline($level=2) 直接拿2层upline

/api/people?$expand=directMembers($count=true) $expand也可以$count哦 

 

 

 

更新 : 2016-02-12 

OData 的继承顺序

parent constructor -> child construtor -> child ExecuteAsync -> child Initialize 

constructor 阶段request is null, child ExecuteAsync and Initialize 内部就可以调用base了,顺序就不太重要了.

 

更新 : 2016-06-03

http://docs.oasis-open.org/odata/odata/v4.0/errata02/os/complete/part1-protocol/odata-v4.0-errata02-os-part1-protocol-complete.html#_The_$format_System

http://docs.oasis-open.org/odata/odata/v4.0/odata-v4.0-part2-url-conventions.html

http://docs.oasis-open.org/odata/odata/v4.0/odata-v4.0-part3-csdl.html

http://www.odata.org/documentation/

 

 

 

 

posted @   兴杰  阅读(630)  评论(0编辑  收藏  举报
(评论功能已被禁用)
编辑推荐:
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
阅读排行:
· 百万级群聊的设计实践
· 全网最简单!3分钟用满血DeepSeek R1开发一款AI智能客服,零代码轻松接入微信、公众号、小程
· .NET 10 首个预览版发布,跨平台开发与性能全面提升
· 《HelloGitHub》第 107 期
· 从文本到图像:SSE 如何助力 AI 内容实时呈现?(Typescript篇)
历史上的今天:
2014-09-28 Entity Framework with MySQL 学习笔记一(insert,update,delete)
点击右上角即可分享
微信分享提示