在常用的三层架构中,通常都是通过数据访问层来修改或者查询数据,一般修改和查询使用的是相同的实体。在一些业务逻辑简单的系统中可能没有什么问题,但是随着系统逻辑变得复杂,用户增多,这种设计就会出现一些性能问题。虽然在DB上可以做一些读写分离的设计,但在业务上如果在读写方面混合在一起的话,仍然会出现一些问题。 本文介绍了命令查询职责分离模式(Command Query Responsibility Segregation,CQRS),该模式从业务上分离修改 (Command,增,删,改,会对系统状态进行修改)和查询(Query,查,不会对系统状态进行修改)的行为。从而使得逻辑更加清晰,便于对不同部分进行针对性的优化。文章首先简要介绍了传统的CRUD方式存在的问题,接着介绍了CQRS模式,最后以一个简单的在线日记系统演示了如何实现CQRS模式。要谈到读写操作,首先我们来看传统的CRUD的问题。 一 CRUD方式的问题 在以前的管理系统中,命令(Command,通常用来更新数据,操作DB)和查询(Query)通常使用的是在数据访问层中Repository中的实体对象(这些对象是对DB中表的映射),这些实体有可能是SQLServer中的一行数据或者多个表。 通常对DB执行的增,删,改,查(CRUD)都是针对的系统的实体对象。如通过数据访问层获取数据,然后通过数据传输对象DTO传给表现层。或者,用户需要更新数据,通过DTO对象将数据传给Model,然后通过数据访问层写回数据库,系统中的所有交互都是和数据查询和存储有关,可以认为是数据驱动(Data-Driven)的,如下图: 对于一些比较简单的系统,使用这种CRUD的设计方式能够满足要求。特别是通过一些代码生成工具及ORM等能够非常方便快速的实现功能。 但是传统的CRUD方法有一些问题: 使用同一个对象实体来进行数据库读写可能会太粗糙,大多数情况下,比如编辑的时候可能只需要更新个别字段,但是却需要将整个对象都穿进去,有些字段其实是不需要更新的。在查询的时候在表现层可能只需要个别字段,但是需要查询和返回整个实体对象。 使用同一实体对象对同一数据进行读写操作的时候,可能会遇到资源竞争的情况,经常要处理的锁的问题,在写入数据的时候,需要加锁。读取数据的时候需要判断是否允许脏读。这样使得系统的逻辑性和复杂性增加,并且会对系统吞吐量的增长会产生影响。 同步的,直接与数据库进行交互在大数据量同时访问的情况下可能会影响性能和响应性,并且可能会产生性能瓶颈。 由于同一实体对象都会在读写操作中用到,所以对于安全和权限的管理会变得比较复杂。 这里面很重要的一个问题是,系统中的读写频率比,是偏向读,还是偏向写,就如同一般的数据结构在查找和修改上时间复杂度不一样,在设计系统的结构时也需要考虑这样的问题。解决方法就是我们经常用到的对数据库进行读写分离。 让主数据库处理事务性的增,删,改操作(Insert,Update,Delete)操作,让从数据库处理查询操作(Select操作),数据库复制被用来将事务性操作导致的变更同步到集群中的从数据库。这只是从DB角度处理了读写分离,但是从业务或者系统上面读和写仍然是存放在一起的。他们都是用的同一个实体对象。 要从业务上将读和写分离,就是接下来要介绍的命令查询职责分离模式。 二 什么是CQRS CQRS最早来自于Betrand Meyer(Eiffel语言之父,开-闭原则OCP提出者)在 Object-Oriented Software Construction 这本书中提到的一种 命令查询分离 (Command Query Separation,CQS) 的概念。其基本思想在于,任何一个对象的方法可以分为两大类: 命令(Command):不返回任何结果(void),但会改变对象的状态。 查询(Query):返回结果,但是不会改变对象的状态,对系统没有副作用。 根据CQS的思想,任何一个方法都可以拆分为命令和查询两部分,比如:
1 |
private int i = 0; |
1 2 3 4 5 |
private int Increase(int value) { i += value; return i; } |
这个方法,我们执行了一个命令即对变量i进行相加,同时又执行了一个Query,即查询返回了i的值,如果按照CQS的思想,该方法可以拆成Command和Query两个方法,如下:
1 2 3 4 5 6 7 8 |
private void IncreaseCommand(int value) { i += value; } private int QueryValue() { return i; } |
操作和查询分离使得我们能够更好的把握对象的细节,能够更好的理解哪些操作会改变系统的状态。当然CQS也有一些缺点,比如代码需要处理多线程的情况。 CQRS是对CQS模式的进一步改进成的一种简单模式。 它由Greg Young在CQRS, Task Based UIs, Event Sourcing agh! 这篇文章中提出。“CQRS只是简单的将之前只需要创建一个对象拆分成了两个对象,这种分离是基于方法是执行命令还是执行查询这一原则来定的(这个和CQS的定义一致)”。 CQRS使用分离的接口将数据查询操作(Queries)和数据修改操作(Commands)分离开来,这也意味着在查询和更新过程中使用的数据模型也是不一样的。这样读和写逻辑就隔离开来了。 使用CQRS分离了读写职责之后,可以对数据进行读写分离操作来改进性能,可扩展性和安全。如下图: 主数据库处理CUD,从库处理R,从库的的结构可以和主库的结构完全一样,也可以不一样,从库主要用来进行只读的查询操作。在数量上从库的个数也可以根据查询的规模进行扩展,在业务逻辑上,也可以根据专题从主库中划分出不同的从库。从库也可以实现成ReportingDatabase,根据查询的业务需求,从主库中抽取一些必要的数据生成一系列查询报表来存储。 使用ReportingDatabase的一些优点通常可以使得查询变得更加简单高效: ReportingDatabase的结构和数据表会针对常用的查询请求进行设计。 ReportingDatabase数据库通常会去正规化,存储一些冗余而减少必要的Join等联合查询操作,使得查询简化和高效,一些在主数据库中用不到的数据信息,在ReportingDatabase可以不用存储。 可以对ReportingDatabase重构优化,而不用去改变操作数据库。 对ReportingDatabase数据库的查询不会给操作数据库带来任何压力。 可以针对不同的查询请求建立不同的ReportingDatabase库。 当然这也有一些缺点,比如从库数据的更新。如果使用SQLServer,本身也提供了一些如故障转移和复制机制来方便部署。 三 什么时候可以考虑CQRS CQRS模式有一些优点: 分工明确,可以负责不同的部分 将业务上的命令和查询的职责分离能够提高系统的性能、可扩展性和安全性。并且在系统的演化中能够保持高度的灵活性,能够防止出现CRUD模式中,对查询或者修改中的某一方进行改动,导致另一方出现问题的情况。 逻辑清晰,能够看到系统中的那些行为或者操作导致了系统的状态变化。 可以从数据驱动(Data-Driven) 转到任务驱动(Task-Driven)以及事件驱动(Event-Driven). 在下场景中,可以考虑使用CQRS模式: 当在业务逻辑层有很多操作需要相同的实体或者对象进行操作的时候。CQRS使得我们可以对读和写定义不同的实体和方法,从而可以减少或者避免对某一方面的更改造成冲突 对于一些基于任务的用户交互系统,通常这类系统会引导用户通过一系列复杂的步骤和操作,通常会需要一些复杂的领域模型,并且整个团队已经熟悉领域驱动设计技术。写模型有很多和业务逻辑相关的命令操作的堆,输入验证,业务逻辑验证来保证数据的一致性。读模型没有业务逻辑以及验证堆,仅仅是返回DTO对象为视图模型提供数据。读模型最终和写模型相一致。 适用于一些需要对查询性能和写入性能分开进行优化的系统,尤其是读/写比非常高的系统,横向扩展是必须的。比如,在很多系统中读操作的请求时远大于写操作。为适应这种场景,可以考虑将写模型抽离出来单独扩展,而将写模型运行在一个或者少数几个实例上。少量的写模型实例能够减少合并冲突发生的情况 适用于一些团队中,一些有经验的开发者可以关注复杂的领域模型,这些用到写操作,而另一些经验较少的开发者可以关注用户界面上的读模型。 对于系统在将来会随着时间不段演化,有可能会包含不同版本的模型,或者业务规则经常变化的系统 需要和其他系统整合,特别是需要和事件溯源Event Sourcing进行整合的系统,这样子系统的临时异常不会影响整个系统的其他部分。 但是在以下场景中,可能不适宜使用CQRS: 领域模型或者业务逻辑比较简单,这种情况下使用CQRS会把系统搞复杂。 对于简单的,CRUD模式的用户界面以及与之相关的数据访问操作已经足够的话,没必要使用CQRS,这些都是一个简单的对数据进行增删改查。 不适合在整个系统中到处使用该模式。在整个数据管理场景中的特定模块中CQRS可能比较有用。但是在有些地方使用CQRS会增加系统不必要的复杂性。 四 CQRS与Event Sourcing的关系 在CQRS中,查询方面,直接通过方法查询数据库,然后通过DTO将数据返回。在操作(Command)方面,是通过发送Command实现,由CommandBus处理特定的Command,然后由Command将特定的Event发布到EventBus上,然后EventBus使用特定的Handler来处理事件,执行一些诸如,修改,删除,更新等操作。这里,所有与Command相关的操作都通过Event实现。这样我们可以通过记录Event来记录系统的运行历史记录,并且能够方便的回滚到某一历史状态。Event Sourcing就是用来进行存储和管理事件的。这里不展开介绍。 五 CQRS的简单实现 CQRS模式在思想上比较简单,但是实现上还是有些复杂。它涉及到DDD,以及Event Sourcing,这里使用codeproject上的 Introduction to CQRS 这篇文章的例子来说明CQRS模式。这个例子是一个简单的在线记日志(Diary)系统,实现了日志的增删改查功能。整体结构如下: 上图很清晰的说明了CQRS在读写方面的分离,在读方面,通过QueryFacade到数据库里去读取数据,这个库有可能是ReportingDB。在写方面,比较复杂,操作通过Command发送到CommandBus上,然后特定的CommandHandler处理请求,产生对应的Event,将Eevnt持久化后,通过EventBus特定的EevntHandler对数据库进行修改等操作。 例子代码可以到codeproject上下载,整体结构如下: […]
View Details1.项目背景 这里简单介绍一下项目需求背景,之前公司的项目基于EF++Repository+UnitOfWork的框架设计的,其中涉及到的技术有RabbitMq消息队列,Autofac依赖注入等常用的.net插件。由于公司的发展,业务不断更新,变得复杂起来,对于数据的实时性、存储容量要求也提高了一个新的高度。数据库上下文DbContext设计的是单例模式,基本上告别了多线程高并发的数据读写能力,看了园子里很多大神的博客,均为找到适合自己当前需求的DbContext的管理方式。总结目前主要的管理方式: 1)DbContext单例模式(长连接)。即公司之前的设计。很明显,这种设计方式无法支持多线程同步数据操作。报各种错误,最常见的,比如:集合已修改,无法进行枚举操作。—弃用 2)Using模式(短连接)。这种模式适合一些对于外键,导航属性不经常使用的场合,由于导航属性是放在上下文缓存中的,一旦上下文释放掉,导航属性就为null。当然,也尝试了其他大神的做法,比如,在上下文释放之前转换为 ToList或者使用饥饿加载的方式(ps:这种方式很不灵活,你总不可能遇到一个类类型就去利用反射加载找到它具有的导航属性吧或者直接InCluding),这些方法依旧没有办法解决目前的困境。也尝试这直接赋值给一个定义的同类 型的变量,但是对于这种带导航的导航的复杂类的深拷贝,没有找到合适的路子,有知道的可以告诉我,非常感谢! 以上两种方式及网上寻找的其他方式都没有解决我的问题。这里先上一下之前的Repository:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 |
1 using System.Data.Entity; 2 using System.Data.Entity.Validation; 3 4 namespace MM.Data.Library.Entityframework 5 { 6 public class EntityFrameworkRepositoryContext : RepositoryContext, IEntityFrameworkRepositoryContext 7 { 8 protected DbContext container; 9 10 public EntityFrameworkRepositoryContext(DbContext container) 11 { 12 this.container = container; 13 } 14 15 public override void RegisterNew<TAggregateRoot>(TAggregateRoot obj) 16 { 17 this.container.Set<TAggregateRoot>().Add(obj); 18 this.IsCommit = false; 19 } 20 21 public override void RegisterModified<TAggregateRoot>(TAggregateRoot obj) 22 { 23 if (this.container.Entry<TAggregateRoot>(obj).State == EntityState.Detached) 24 { 25 this.container.Set<TAggregateRoot>().Attach(obj); 26 } 27 this.container.Entry<TAggregateRoot>(obj).State = EntityState.Modified; 28 this.IsCommit = false; 29 } 30 31 public override void RegisterDeleted<TAggregateRoot>(TAggregateRoot obj) 32 { 33 this.container.Set<TAggregateRoot>().Remove(obj); 34 this.IsCommit = false; 35 } 36 37 public override void Rollback() 38 { 39 this.IsCommit = false; 40 } 41 42 protected override void DoCommit() 43 { 44 if (!IsCommit) 45 { 46 //var count = container.SaveChanges(); 47 //IsCommit = true; 48 try 49 { 50 var count = container.SaveChanges(); 51 IsCommit = true; 52 } 53 catch (DbEntityValidationException dbEx) 54 { 55 foreach (var validationErrors in dbEx.EntityValidationErrors) 56 { 57 foreach (var validationError in validationErrors.ValidationErrors) 58 { 59 } 60 } 61 IsCommit = false; 62 } 63 } 64 } 65 66 public System.Data.Entity.DbContext DbContext 67 { 68 get { return container; } 69 } 70 71 public override void Dispose() 72 { 73 if (container != null) 74 container.Dispose(); 75 } 76 } 77 } |
View Code 2.设计思路及方法 从上下文的单例模式来看,所要解决的问题无非就是在多线程对数据库写操作上面。只要在这上面做手脚,问题应该就能引刃而解。我的想法是将所有的要修改的数据分别放入UpdateList,InsertList,DeleteList三个集合中去,然后提交到数据库保存。至于DbContext的管理,通过一个数据库工厂获取,保证每一个数据库的连接都是唯一的,不重复的(防止发生类似这种错误:正在创建模型,此时不可使用上下文。),用的时候直接去Factory拿。等到数据库提交成功后,清空集合数据。看起来,实现起来很容易,但是因为还涉及到其他技术,比如Redis。所以实现过程费劲。也许我的能力还差很多。总之,废话不多说,直接上部分实现代码: 数据库上下文建立工厂:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
1 /// <summary> 2 /// 数据库建立工厂 3 /// Modify By: 4 /// Modify Date: 5 /// Modify Reason: 6 /// </summary> 7 public sealed class DbFactory 8 { 9 public static IDbContext GetCurrentDbContext(string connectstring,string threadName) 10 { 11 lock (threadName) 12 { 13 //CallContext:是线程内部唯一的独用的数据槽(一块内存空间) 14 //传递Context进去获取实例的信息,在这里进行强制转换。 15 var Context = CallContext.GetData("Context") as IDbContext; 16 17 if (Context == null) //线程在内存中没有此上下文 18 { 19 var Scope = UnitoonIotContainer.Container.BeginLifetimeScope(); 20 //如果不存在上下文 创建一个(自定义)EF上下文 并且放在数据内存中去 21 Context = Scope.Resolve<IDbContext>(new NamedParameter("connectionString", connectstring)); 22 CallContext.SetData("Context", Context); 23 } 24 else 25 { 26 27 if (!Context.ConnectionString.Equals(connectstring)) 28 { 29 var Scope = UnitoonIotContainer.Container.BeginLifetimeScope(); 30 //如果不存在上下文 创建一个(自定义)EF上下文 并且放在数据内存中去 31 Context = Scope.Resolve<IDbContext>(new NamedParameter("connectionString", connectstring)); 32 CallContext.SetData("Context", Context); 33 } 34 } 35 return Context; 36 } 37 } 38 39 } |
View Code Repository:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 |
1 public class RepositoryBase<T, TContext> : IRepositoryBase<T> where T : BaseEntity 2 3 where TContext : ContextBase, IDbContext, IDisposable, new() 4 { 5 public List<T> InsertList { get; set; } 6 public List<T> DeleteList { get; set; } 7 public List<T> UpdateList { get; set; } 8 9 #region field 10 11 protected readonly string Connectstring; 12 ///// <summary> 13 ///// </summary> 14 //protected static IDbContext Context; 15 protected IDbContext dbContext; 16 private static readonly ILifetimeScope Scope; 17 public static int xcount = 0; 18 /////// <summary> 19 /////// </summary> 20 //protected readonly DbSet<T> Dbset; 21 22 #endregion 23 24 #region ctor 25 26 static RepositoryBase() 27 { 28 Scope = UnitoonIotContainer.Container.BeginLifetimeScope(); 29 } 30 31 /// <summary> 32 /// 使用默认连接字符串name=connName 33 /// </summary> 34 public RepositoryBase() : this("") 35 { 36 } 37 38 /// <summary> 39 /// 构造函数 40 /// </summary> 41 /// <param name="connectionString">连接字符串</param> 42 public RepositoryBase(string connectionString) 43 { 44 InsertList = new List<T>(); 45 DeleteList = new List<T>(); 46 UpdateList = new List<T>(); 47 48 49 //*****做以下调整,初始化,建立所有数据库连接,保持长连接状态,在用的时候去判断使用连接 50 51 52 //todo 待处理 53 54 55 if (string.IsNullOrWhiteSpace(connectionString)) 56 { 57 var name = DataBase.GetConnectionString(Activator.CreateInstance<T>().DbType); 58 //Context= ContextHelper.GetDbContext(Activator.CreateInstance<T>().DbType); 59 connectionString = name; 60 } 61 Connectstring = connectionString; 62 63 // Context = Scope.Resolve<IDbContext>(new NamedParameter("connectionString", Connectstring)); 64 65 //Context = new TContext { ConnectionString = connectionString }; 66 67 68 // Dbset = Context.Set<T>(); 69 70 //var loggerFactory = ((DbContext)Context).GetService<ILoggerFactory>(); 71 //loggerFactory.AddProvider(new DbLoggerProvider(Console.WriteLine)); 72 //loggerFactory.AddConsole(minLevel: LogLevel.Warning); 73 } 74 75 //public RepositoryBase(TContext context) 76 //{ 77 // Context = context; 78 // Dbset = context.Set<T>(); 79 //} 80 81 #endregion 82 83 #region Method 84 85 //public virtual IDbContext GetDbContext(ILifetimeScope scope) 86 //{ 87 88 //} 89 90 #region Check Model 91 92 /// <summary> 93 /// 校正Model 94 /// </summary> 95 protected virtual void ValidModel() 96 { 97 } 98 99 #endregion 100 101 #region Update 102 103 public virtual void Update(T entity) 104 { 105 Check.NotNull(entity, "entity"); 106 UpdateList.Add(entity); 107 //context.Set<T>().Update(entity); 108 } 109 110 public virtual void Update(IEnumerable<T> entities) 111 { 112 Check.NotNull(entities, "entities"); 113 UpdateList.AddRange(entities); 114 } 115 116 #endregion 117 118 #region PageList 119 120 public virtual IEnumerable<T> GetPageList(Expression<Func<T, bool>> where, Expression<Func<T, object>> orderBy, 121 int pageIndex, int pageSize) 122 { 123 //todo 124 } 125 126 #endregion 127 128 #region Insert 129 130 public virtual void Add(T entity) 131 { 132 Check.NotNull(entity, "entity"); 133 //排除已经存在的项(对于多线程没有任何用处) 134 if (!InsertList.Exists(e => e.Equals(entity))) 135 { 136 InsertList.Add(entity); 137 } 138 139 } 140 141 public virtual void Add(IEnumerable<T> entities) 142 { 143 Check.NotNull(entities, "entities"); 144 InsertList.AddRange(entities); 145 } 146 147 public void BulkInsert(IEnumerable<T> entities) 148 { 149 Check.NotNull(entities, "entities"); 150 InsertList.AddRange(entities); 151 } 152 153 #endregion 154 155 #region Delete 156 157 public virtual void Delete(int id) 158 { 159 var entity = GetById(id); 160 Delete(entity); 161 // throw new NotImplementedException("Delete(int id)"); 162 } 163 164 public virtual void Delete(string id) 165 { 166 throw new NotImplementedException("Delete(string id)"); 167 } 168 169 public virtual void Delete(T entity) 170 { 171 Check.NotNull(entity, "entity"); 172 DeleteList.Add(entity); 173 } 174 175 public virtual void Delete(IEnumerable<T> entities) 176 { 177 Check.NotNull(entities, "entities"); 178 foreach (var x1 in DeleteList) 179 { 180 DeleteList.Add(x1); 181 } 182 } 183 184 public virtual void Delete(Expression<Func<T, bool>> where) 185 { 186 var list = DeleteList.Where(where.Compile()); 187 Delete(list); 188 } 189 190 #endregion 191 192 #region Commit 193 194 public int Commit() 195 { 196 ValidModel(); 197 //var x = Activator.CreateInstance<T>(); 198 //Context = ContextHelper.GetDbContext(x.DbType); 199 //using (var scope = UnitoonIotContainer.Container.BeginLifetimeScope()) 200 //{ 201 // var context = scope.Resolve<IDbContext>(new NamedParameter("connectionString", Connectstring)); 202 203 //var loggerFactory = Activator.CreateInstance<ILoggerFactory>();// ((DbContext)context).GetService<ILoggerFactory>(); 204 //loggerFactory.AddProvider(new DbLoggerProvider(Console.WriteLine)); 205 dbContext = DbFactory.GetCurrentDbContext(Connectstring, Thread.CurrentThread.Name); 206 var dbset = dbContext.Set<T>(); 207 if (InsertList != null && InsertList.Any()) 208 { 209 List<T> InsertNewList = InsertList.Distinct(new PropertyComparer<T>("Id")).ToList();//按照特定规则排除重复项 210 dbset.AddRange(InsertNewList); 211 } 212 213 if (DeleteList != null && DeleteList.Any()) 214 DeleteList.ForEach(t => 215 { 216 // Context.Entry(t).State = EntityState.Detached;//将之前上下文跟踪的状态丢弃 217 //dbContext.Entry(t).State = EntityState.Detached;//将之前上下文跟踪的状态丢弃 218 dbset.Attach(t); 219 dbset.Remove(t); 220 }); 221 if (UpdateList != null && UpdateList.Any()) 222 { 223 List<T> UpdateNewList = UpdateList.Distinct(new PropertyComparer<T>("Id")).ToList();//按照特定规则排除重复项 224 UpdateNewList.ForEach(t => 225 { 226 //Context.Entry(t).State = EntityState.Detached;//将之前上下文跟踪的状态丢弃 227 // dbContext.Entry(t).State = EntityState.Detached;//将之前上下文跟踪的状态丢弃 228 dbContext.Entry(t).State = EntityState.Modified; 229 });//.UpdateRange(UpdateNewList); 230 } 231 var result = 0; 232 try 233 { 234 result = dbContext.SaveChanges(); 235 } 236 catch (Exception ex) 237 { 238 239 // throw; 240 } 241 242 if (InsertList != null && InsertList.Any()) 243 InsertList.Clear(); 244 if (DeleteList != null && DeleteList.Any()) 245 DeleteList.Clear(); 246 if (UpdateList != null && UpdateList.Any()) 247 UpdateList.Clear(); 248 return result; 249 //} 250 } 251 252 public async Task<int> CommitAsync() 253 { 254 ValidModel(); 255 //todo 256 257 } 258 259 #endregion 260 261 #region Query 262 public IQueryable<T> Get() 263 { 264 return GetAll().AsQueryable(); 265 } 266 //public virtual T Get(Expression<Func<T, bool>> @where) 267 //{ 268 // using (var scope = UnitoonIotContainer.Container.BeginLifetimeScope()) 269 // { 270 271 // var context = scope.Resolve<IDbContext>(new NamedParameter("connectionString", Connectstring)); 272 // var dbset = context.Set<T>(); 273 // return dbset.FirstOrDefault(where); 274 // } 275 //} 276 277 public virtual async Task<T> GetAsync(Expression<Func<T, bool>> @where) 278 { 279 //todo 280 } 281 282 public virtual T GetById(int id) 283 { 284 throw new NotImplementedException("GetById(int id)"); 285 } 286 287 public virtual async Task<T> GetByIdAsync(int id) 288 { 289 throw new NotImplementedException("GetById(int id)"); 290 } 291 292 public virtual T GetById(string id) 293 { 294 throw new NotImplementedException("GetById(int id)"); 295 } 296 297 public virtual async Task<T> GetByIdAsync(string id) 298 { 299 throw new NotImplementedException("GetById(int id)"); 300 } 301 302 public virtual T Get(Expression<Func<T, bool>> @where)//, params string[] includeProperties 303 { 304 //var scope = UnitoonIotContainer.Container.BeginLifetimeScope(); 305 //{ 306 // var context = scope.Resolve<IDbContext>(new NamedParameter("connectionString", Connectstring)); 307 //Thread.Sleep(50); 308 //lock (Context) 309 { 310 dbContext= DbFactory.GetCurrentDbContext(Connectstring, Thread.CurrentThread.Name); 311 var dbset = dbContext.Set<T>().Where(e => !e.IsDeleted).AsQueryable(); 312 var entity = dbset.FirstOrDefault(where); 313 //test 314 // Context.Entry(entity).State = EntityState.Detached;//将之前上下文跟踪的状态丢弃 315 return entity; 316 } 317 318 319 //} 320 } 321 322 public virtual IEnumerable<T> GetAll() 323 { 324 //Thread.Sleep(50); 325 //lock (Context) 326 { 327 dbContext = DbFactory.GetCurrentDbContext(Connectstring, Thread.CurrentThread.Name); 328 var dbset = dbContext.Set<T>().Where(e => !e.IsDeleted); 329 //test 330 //dbset.ToList().ForEach(t => 331 //{ 332 // Context.Entry(t).State = EntityState.Detached;//将之前上下文跟踪的状态丢弃 333 //}); 334 335 return dbset; 336 } 337 338 339 340 341 //var scope = UnitoonIotContainer.Container.BeginLifetimeScope(); 342 343 // var context = scope.Resolve<IDbContext>(new NamedParameter("connectionString", Connectstring)); 344 345 } 346 347 public async virtual Task<IEnumerable<T>> GetAllAsync() 348 { 349 //todo 350 } 351 352 public virtual IEnumerable<T> GetMany(Expression<Func<T, bool>> where) 353 { 354 //using (var scope = UnitoonIotContainer.Container.BeginLifetimeScope()) 355 //{ 356 // var context = scope.Resolve<IDbContext>(new NamedParameter("connectionString", Connectstring)); 357 358 // var dbset = context.Set<T>(); 359 //Thread.Sleep(50); 360 //lock (Context) 361 { 362 dbContext = DbFactory.GetCurrentDbContext(Connectstring, Thread.CurrentThread.Name); 363 var dbset = dbContext.Set<T>().Where(e => !e.IsDeleted); 364 //test 365 //dbset.ToList().ForEach(t => 366 //{ 367 // Context.Entry(t).State = EntityState.Detached;//将之前上下文跟踪的状态丢弃 368 //}); 369 return dbset.Where(@where).ToList(); 370 } 371 372 //} 373 } 374 375 public virtual async Task<IEnumerable<T>> GetManyAsync(Expression<Func<T, bool>> where) 376 { 377 //todo 378 } 379 380 public virtual IEnumerable<T> IncludeSubSets(params Expression<Func<T, object>>[] includeProperties) 381 { 382 //todo 383 } 384 385 #region navigation 386 /// <summary> 387 /// 加载导航 388 /// </summary> 389 /// <param name="where"></param> 390 /// <param name="includeProperties"></param> 391 /// <returns></returns> 392 //public virtual T Get(Expression<Func<T, bool>> @where, params Expression<Func<T, object>>[] includeProperties) 393 //{ 394 // using (var scope = UnitoonIotContainer.Container.BeginLifetimeScope()) 395 // { 396 397 // var context = scope.Resolve<IDbContext>(new NamedParameter("connectionString", Connectstring)); 398 // var dbset = context.Set<T>(); 399 // var query = includeProperties.Aggregate<Expression<Func<T, object>>, IQueryable<T>>(dbset, 400 // (current, includeProperty) => current.Include(includeProperty)); 401 // return query.FirstOrDefault(where); 402 // } 403 //} 404 405 //public virtual T Get(Expression<Func<T, bool>> @where)//, params string[] includeProperties 406 //{ 407 // //反射获取导航 408 // var includeProperties = 409 // Activator.CreateInstance<T>().GetType().GetProperties().Where(p => p.GetMethod.IsVirtual).Select(e => e.Name).ToArray(); 410 411 //todo 412 //} 413 #endregion 414 public List<TDynamicEntity> GetDynamic<TTable, TDynamicEntity>(Expression<Func<TTable, object>> selector, 415 Func<object, TDynamicEntity> maker) where TTable : class 416 { 417 //todo 418 } 419 420 public List<TDynamicEntity> GetDynamic<TTable, TDynamicEntity>(Func<TTable, object> selector, 421 Func<object, TDynamicEntity> maker) where TTable : class 422 { 423 //todo 424 425 } 426 427 #endregion 428 429 #region Count 430 431 public virtual async Task<int> CountAsync() 432 { 433 //todo 434 } 435 436 public virtual async Task<int> CountByAsync(Expression<Func<T, bool>> where) 437 { 438 //todo 439 } 440 441 #endregion 442 443 #region Exists 444 445 public virtual bool Exists(string id) 446 { 447 throw new NotImplementedException(); 448 } 449 450 public virtual bool Exists(int id) 451 { 452 throw new NotImplementedException(); 453 } 454 455 public virtual async Task<bool> ExistsAsync(string id) 456 { 457 throw new NotImplementedException(); 458 } 459 460 public virtual async Task<bool> ExistsAsync(int id) 461 { 462 throw new NotImplementedException(); 463 } 464 465 public virtual bool Exists(Expression<Func<T, bool>> @where) 466 { 467 throw new NotImplementedException(); 468 } 469 470 public virtual async Task<bool> ExistsAsync(Expression<Func<T, bool>> @where) 471 { 472 throw new NotImplementedException(); 473 } 474 475 #endregion 476 477 478 #endregion 479 } |
http://blog.csdn.net/wodeai1235/article/details/54923072
View Detailspublic enum StringComparison { CurrentCulture, CurrentCultureIgnoreCase, InvariantCulture, InvariantCultureIgnoreCase, Ordinal, OrdinalIgnoreCase } CurrentCulture 使用区域敏感排序规则和当前区域比较字符串。 CurrentCultureIgnoreCase 使用区域敏感排序规则、当前区域来比较字符串,同时忽略被比较字符串的大小写。 InvariantCulture 使用区域敏感排序规则和固定区域比较字符串。 InvariantCultureIgnoreCase 使用区域敏感排序规则、固定区域来比较字符串,同时忽略被比较字符串的大小写。 Ordinal 使用序号排序规则比较字符串。 OrdinalIgnoreCase 使用序号排序规则并忽略被比较字符串的大小写,对字符串进行比较。 1.首先是StringComparison.Ordinal 在进行调用String.Compare(string1,string2,StringComparison.Ordinal)的时候是进行非语言(non-linguistic)上的比较,API运行时将会对两个字符串进行byte级别的比较,因此这种比较是比较严格和准确的,并且在性能上也很好,一般通过StringComparison.Ordinal来进行比较比使用String.Compare(string1,string2)来比较要快10倍左右.(可以写一个简单的小程序验证,这个挺让我惊讶,因为平时使用String.Compare从来就没想过那么多).StringComparison.OrdinalIgnoreCase就是忽略大小写的比较,同样是byte级别的比较.性能稍弱于StringComparison.Ordinal. 2.StringComparison.CurrentCulture 是在当前的区域信息下进行比较,这是String.Compare在没有指定StringComparison的时候默认的比较方式.例子如下: Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US"); //当前的区域信息是美国 string s1 = "visualstudio"; string s2 = "windows"; Console.WriteLine(String.Compare(s1, s2,StringComparison.CurrentCulture)); //输出"-1" Thread.CurrentThread.CurrentCulture = new CultureInfo("sv-SE"); //当前的区域信息是瑞典 Console.WriteLine(String.Compare(s1, s2,StringComparison.CurrentCulture)); //输出"1" StringComarison.CurrentCultureIgnoreCase指在当前区域信息下忽略大小写的比较. 3.StringComarison.InvariantCulture 使用StringComarison.InvariantCulture来进行字符串比较,在任何系统中(不同的culture)比较都将得到相同的结果,他是使用CultureInfo.InvariantCulture的静态成员CompareInfo来进行比较操作的.例子如下: Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US"); //当前的区域信息是美国 string s1 = "visualstudio"; string s2 = "windows"; Console.WriteLine(String.Compare(s1, s2,StringComparison.InvariantCulture)); //输出"-1" Thread.CurrentThread.CurrentCulture = new CultureInfo("sv-SE"); //当前的区域信息是瑞典 Console.WriteLine(String.Compare(s1, s2,StringComparison.InvariantCulture)); //输出"-1" 参考文章:http://msdn.microsoft.com/netframework/default.aspx?pull=/library/en-us/dndotnet/html/StringsinNET20.asp from:http://www.cnblogs.com/zhw511006/archive/2010/07/09/1774591.html
View Details执行SqlDataReader.Read之后,如果还想用另一个SqlCommand执行Insert或者Update操作的话,会得到一个错误提示:There is already an open DataReader associated with this Command which must be closed first.,然后一般就会产生数据保存失败的异常。 解决方法是在ConnectionString中加上一个参数“MultipleActiveResultSets”, 将其值设置为true。
1 |
SqlConnection conn = new SqlConnection("server=s01;database=MOULTONWEB;uid=sa;pwd=cn1234567890;MultipleActiveResultSets=True |
MultipleActiveResultSets属性详解 ADO.NET 1.x 利用SqlDataReader读取数据,针对每个结果集需要一个独立的连接。当然,你还必须管理这些连接并且要付出相应的内存和潜在的应用程序中的高度拥挤的瓶颈代价-特别是在数据集中的Web应用程序中。 ADO.NET 2.的一个新特征多数据结果集(Multiple Active Result Sets,简称MARS)-它允许在单个连接上执行多重的数据库查询或存储过程。这样的结果是,你能够在单个连接上得到和管理多个、仅向前引用的、只读的结果集。目前实现这个功能的数据库只有Sql Server 2005。所以当我们针对Sql Sever 2005的时候,需要重新审视DataReader对象的使用。使用SqlServer 2005,可以在一个Command对象上同时打开多个DataReader,节约数据库联接所耗费的服务器资源,在实际开发中普遍存在的一种典型的从数据库中读写数据的情形是,你可以使用多重连接而现在只用一个连接就足够了。例如,如果你有一些来自于几个表中的数据-它们不能被联结到一个查询中,那么你就会有多重的连接-每个连接都有一个与之相关连的命令用于读取数据。同样,如果你正在向一个表写数据,那么你需要另外一个连接或连接集合-如果有多个表要被更新的话。 例如下面的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
//MultipleActiveResultSets=true打开联接 string connstr = "server=(local);database=northwind;integrated security=true;MultipleActiveResultSets=true"; SqlConnection conn = new SqlConnection(connstr); conn.Open(); SqlCommand cmd1 = new SqlCommand("select * from customers", conn); SqlCommand cmd2 = new SqlCommand("select * from orders", conn); SqlDataReader rdr1 = cmd1.ExecuteReader(); // next statement causes an error prior to SQL Server 2005 SqlDataReader rdr2 = cmd2.ExecuteReader(); // now you can reader from rdr1 and rdr2 at the same time. conn.Close(); |
近期的一个项目是关于不同数据库同步的操作,考虑到数据的及时性,应用程序的性能,在数据库链接字符串中加入MultipleActiveResultSets; MultipleActiveResultSets的作用是指定多活动的结果集是否与指定的链接相互关联;类型是bool类型;true代表与指定的链接关联;false代表与指定的链接不关联;默认是false; from:http://www.cnblogs.com/sdusrz/p/4433108.html
View Details命名空间: System.Web 程序集: System.Web(位于 System.Web.dll) 事件 名称 说明 · AcquireRequestState 当 ASP.NET 获取与当前的请求相关联的当前状态 (例如,会话状态)。 · AuthenticateRequest 当安全模块已建立的用户标识时出现。 · AuthorizeRequest 安全模块已验证用户身份验证时发生。 · BeginRequest 作为执行的 HTTP 管道链中的第一个事件发生,当 ASP.NET 的请求做出响应。 · Disposed 释放应用程序时发生。 · EndRequest 作为执行的 HTTP 管道链中的最后一个事件发生,当 ASP.NET 的请求做出响应。 · Error 当引发未处理的异常时发生。 · LogRequest ASP.NET 执行当前请求的任何日志记录之前发生。 · MapRequestHandler 此 API 支持 产品 基础结构,不应从代码直接使用。 在选择该处理程序对请求作出响应时发生。 · PostAcquireRequestState 获取与当前的请求相关联的请求状态 (例如,会话状态) 时发生。 · PostAuthenticateRequest 当安全模块已建立的用户标识时出现。 · PostAuthorizeRequest 当前请求的用户已被授权时发生。 · PostLogRequest 当 ASP.NET 已完成处理的事件处理程序时发生 LogRequest 事件。 · PostMapRequestHandler 当 ASP.NET 已映射到相应的事件处理程序的当前请求时出现。 · PostReleaseRequestState 当 ASP.NET 已完成执行所有请求事件处理程序和存储数据的请求状态时发生。 · PostRequestHandlerExecute 当 ASP.NET 事件处理程序 (例如,一个页面或 XML Web 服务) 完成执行时发生。 · PostResolveRequestCache ASP.NET […]
View Details什么是KT库? KT它是一个免费的、开源的(采用LGPL开源协议)函数库。它是Kingthy的个人开发库,它也可以算是一个小的开发框架包。 KT里包含有什么? KT.Core KT库的核心函数存放地方 KT.Core.Data 目前只有一个CSVTextReader对象,用于处理CSV格式的文本数据 KT.Core.Extensions 存放各种类型数据的相关扩展方法 KT.Core.Net 存放处理与网络有关的对象 KT.Core.Net.Mail 存放处理与电子邮件相关的数据对象 KT.Core.ObjectPool 存放与对象池相关的数据对象 KT.Framework KT库的简易开发框架 KT.Framework.Database 存放的是与数据库有关的已封装的数据对象 KT.Framework.Web 存放的是基于VTemplate模板引擎的Web框架封装 KT的使用环境是什么? 运行环境:.NET 4.0 开发环境:VS2010 我在哪里可以下载获取到最新的KT代码? KT目前采用托管于CodePlex开源网站,所以你可以从以下网址获取最新版本的KT库 项目地址:http://kt.codeplex.com/ from:http://www.cnblogs.com/kingthy/archive/2011/08/08/2130973.html
View Details阅读目录 一、get请求 1、基础类型参数 2、实体作为参数 3、数组作为参数 4、“怪异”的get请求 二、post请求 1、基础类型参数 2、实体作为参数 3、数组作为参数 4、后台发送请求参数的传递 三、put请求 1、基础类型参数 2、实体作为参数 3、数组作为参数 四、delete请求 五、总结 正文 前言:还记得刚使用WebApi那会儿,被它的传参机制折腾了好久,查阅了半天资料。如今,使用WebApi也有段时间了,今天就记录下API接口传参的一些方式方法,算是一个笔记,也希望能帮初学者少走弯路。本篇针对初初使用WebApi的同学们,比较基础,有兴趣的且看看。 WebApi系列文章 C#进阶系列——WebApi接口测试工具:WebApiTestClient C#进阶系列——WebApi 跨域问题解决方案:CORS C#进阶系列——WebApi身份认证解决方案:Basic基础认证 C#进阶系列——WebApi接口传参不再困惑:传参详解 C#进阶系列——WebApi接口返回值不困惑:返回值类型详解 C#进阶系列——WebApi异常处理解决方案 C#进阶系列——WebApi区域Area使用小结 本篇打算通过get、post、put、delete四种请求方式分别谈谈基础类型(包括int/string/datetime等)、实体、数组等类型的参数如何传递。 一、get请求 对于取数据,我们使用最多的应该就是get请求了吧。下面通过几个示例看看我们的get请求参数传递。 1、基础类型参数
1 2 3 4 5 |
[HttpGet] public string GetAllChargingData(int id, string name) { return "ChargingData" + id; } |
1 2 3 4 5 6 7 8 9 10 |
$.ajax({ type: "get", url: "http://localhost:27221/api/Charging/GetAllChargingData", data: { id: 1, name: "Jim", bir: "1988-09-11"}, success: function (data, status) { if (status == "success") { $("#div_test").html(data); } } }); |
参数截图效果 这是get请求最基础的参数传递方式,没什么特别好说的。 2、实体作为参数 如果我们在get请求时想将实体对象做参数直接传递到后台,是否可行呢?我们来看看。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
public class TB_CHARGING { /// <summary> /// 主键Id /// </summary> public string ID { get; set; } /// <summary> /// 充电设备名称 /// </summary> public string NAME { get; set; } /// <summary> /// 充电设备描述 /// </summary> public string DES { get; set; } /// <summary> /// 创建时间 /// </summary> public DateTime CREATETIME { get; set; } } |
1 2 3 4 5 |
[HttpGet] public string GetByModel(TB_CHARGING oData) { return "ChargingData" + oData.ID; } |
1 2 3 4 5 6 7 8 9 10 11 |
$.ajax({ type: "get", url: "http://localhost:27221/api/Charging/GetByModel", contentType: "application/json", data: { ID: "1", NAME: "Jim", CREATETIME: "1988-09-11" }, success: function (data, status) { if (status == "success") { $("#div_test").html(data); } } }); |
测试结果 由上图可知,在get请求时,我们直接将json对象当做实体传递后台,后台是接收不到的。这是为什么呢?我们来看看对应的http请求 原来,get请求的时候,默认是将参数全部放到了url里面直接以string的形式传递的,后台自然接不到了。 原因分析:还记得有面试题问过get和post请求的区别吗?其中有一个区别就是get请求的数据会附在URL之后(就是把数据放置在HTTP协议头中),而post请求则是放在http协议包的包体中。 根据园友们的提议,Get请求的时候可以在参数里面加上[FromUri]即可直接得到对象。还是贴上代码:
1 2 3 4 5 6 7 |
var postdata = { ID: "1", NAME: "Jim", CREATETIME: "1988-09-11" }; $.ajax({ type: "get", url: "http://localhost:27221/api/Charging/GetAllChargingData", data: postdata, success: function (data, status) { } }); |
1 2 3 4 5 |
[HttpGet] public string GetAllChargingData([FromUri]TB_CHARGING obj) { return "ChargingData" + obj.ID; } |
得到结果: 如果你不想使用[FromUri]这些在参数里面加特性的这种“怪异”写法,也可以采用先序列化,再在后台反序列的方式。
1 2 3 4 5 6 7 8 9 10 11 |
$.ajax({ type: "get", url: "http://localhost:27221/api/Charging/GetByModel", contentType: "application/json", data: { strQuery: JSON.stringify({ ID: "1", NAME: "Jim", CREATETIME: "1988-09-11" }) }, success: function (data, status) { if (status == "success") { $("#div_test").html(data); } } }); |
1 2 3 4 5 6 |
[HttpGet] public string GetByModel(string strQuery) { TB_CHARGING oData = Newtonsoft.Json.JsonConvert.DeserializeObject<TB_CHARGING>(strQuery); return "ChargingData" + oData.ID; } |
这样在后台得到我们序列化过的对象,再通过反序列化就能得到对象。 在url里面我们可以看到它自动给对象加了一个编码: 至于还有园友们提到http://www.asp.net/web-api/overview/formats-and-model-binding/parameter-binding-in-aspnet-web-api的model binder这种方式,博主看了下,觉得略复杂。有兴趣的也可以试试。至于用哪一种方式传递对象,园友们可以自行选择。 3、数组作为参数 一般get请求不建议将数组作为参数,因为我们知道get请求传递参数的大小是有限制的,最大1024字节,数组里面内容较多时,将其作为参数传递可能会发生参数超限丢失的情况。 4、“怪异”的get请求 为什么会说get请求“怪异”呢?我们先来看看下面的两种写法对比。 (1)WebApi的方法名称以get开头
1 2 3 4 5 6 7 8 9 10 11 |
$.ajax({ type: "get", url: "http://localhost:27221/api/Charging/GetByModel", contentType: "application/json", data: { strQuery: JSON.stringify({ ID: "1", NAME: "Jim", CREATETIME: "1988-09-11" }) }, success: function (data, status) { if (status == "success") { $("#div_test").html(data); } } }); |
1 2 3 4 5 6 |
[HttpGet] public string GetByModel(string strQuery) { TB_CHARGING oData = Newtonsoft.Json.JsonConvert.DeserializeObject<TB_CHARGING>(strQuery); return "ChargingData" + oData.ID; } |
这是标准写法,后台加[HttpGet],参数正常得到: 为了对比,我将[HttpGet]去掉,然后再调用
1 2 3 4 5 6 |
//[HttpGet] public string GetByModel(string strQuery) { TB_CHARGING oData = Newtonsoft.Json.JsonConvert.DeserializeObject<TB_CHARGING>(strQuery); return "ChargingData" + oData.ID; } |
貌似没有任何问题!有人就想,那是否所有的get请求都可以省略掉[HttpGet]这个标注呢。我们试试便知。 (2)WebApi的方法名称不以get开头 我们把之前的方法名由GetByModel改成FindByModel,这个再正常不过了,很多人查询就不想用Get开头,还有直接用Query开头的。这个有什么关系吗?有没有关系,我们以事实说话。
1 2 3 4 5 6 7 8 9 10 11 |
$.ajax({ type: "get", url: "http://localhost:27221/api/Charging/FindByModel", contentType: "application/json", data: { strQuery: JSON.stringify({ ID: "1", NAME: "Jim", CREATETIME: "1988-09-11" }) }, success: function (data, status) { if (status == "success") { $("#div_test").html(data); } } }); |
1 2 3 4 5 6 |
[HttpGet] public string FindByModel(string strQuery) { TB_CHARGING oData = Newtonsoft.Json.JsonConvert.DeserializeObject<TB_CHARGING>(strQuery); return "ChargingData" + oData.ID; } |
貌似又可行,没有任何问题啊。根据上面的推论,我们去掉[HttpGet]也是可行的,好,我们注释掉[HttpGet],运行起来试试。 结果是不进断点,有些人不信,我们在浏览器里面看看http请求: 呵呵,这就奇怪了,就改了个方法名,至于这样么?还真至于! 博主的理解是:方法名以Get开头,WebApi会自动默认这个请求就是get请求,而如果你以其他名称开头而又不标注方法的请求方式,那么这个时候服务器虽然找到了这个方法,但是由于请求方式不确定,所以直接返回给你405——方法不被允许的错误。 最后结论:所有的WebApi方法最好是加上请求的方式([HttpGet]/[HttpPost]/[HttpPut]/[HttpDelete]),不要偷懒,这样既能防止类似的错误,也有利于方法的维护,别人一看就知道这个方法是什么请求。 这也就是为什么很多人在园子里面问道为什么方法名不加[HttpGet]就调用不到的原因! 二、post请求 在WebApi的RESETful风格里面,API服务的增删改查,分别对应着http的post/delete/put/get请求。我们下面就来说说post请求参数的传递方式。 1、基础类型参数 post请求的基础类型的参数和get请求有点不一样,我们知道get请求的参数是通过url来传递的,而post请求则是通过http的请求体中传过来的,WebApi的post请求也需要从http的请求体里面去取参数。 (1)错误的写法
1 2 3 4 5 6 7 8 9 10 |
$.ajax({ type: "post", url: "http://localhost:27221/api/Charging/SaveData", data: { NAME: "Jim" }, success: function (data, status) { if (status == "success") { $("#div_test").html(data); } } }); |
1 2 3 4 5 |
[HttpPost] public bool SaveData(string NAME) { return true; } |
这是一种看上去非常正确的写法,可是实际情况是: (2)正确的用法
1 2 3 4 5 6 |
$.ajax({ type: "post", url: "http://localhost:27221/api/Charging/SaveData", data: { "": "Jim" }, success: function (data, status) {} }); |
[…]
View Details一、路由介绍 ASP.NET Web API路由是整个API的入口。我们访问某个资源就是通过路由映射找到对应资源的URL。通过URL来获取资源的。 对于ASP.NET Web API内部实现来讲,我们的请求最终将定位到一个具体的Action上。所以说,ASP.NET Web API路由就是把客户端请求映射到对应的Action上的过程。 二、两种路由模式 2.1 模板路由 模板路由是ASP.NET Web API默认提供的路由。下面我们就简单讲解此中路由的用法。 默认模板路由 模板路由使用前需要定义路由模板。如下面默认的路由模板:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
using System; using System.Collections.Generic; using System.Linq; using System.Web.Http; namespace Supernova.Webapi { public static class WebApiConfig { public static void Register(HttpConfiguration config) { // Web API 配置和服务 // Web API 路由 config.MapHttpAttributeRoutes(); config.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{controller}/{id}", defaults: new { id = RouteParameter.Optional } ); } } } |
此模板路由是新建项目默认生成的,在App_Start文件夹下。 我们可以看到此模板的URL格式是api/{controller}/{id}。api代表在资源前面要带上api目录,controller代表请求资源的控制器名称。id代表一条资源的id,id 是可选的。这种默认的模板是不带action的,所以它是以请求方式来区分资源的,我们必须在action上添加请求方式特性加以区分。 1.我们添加一个测试控制器api。
1 2 3 4 5 6 7 |
public class TestController : ApiController { public object Get1() { return "d1"; } } |
用fiddldr调试如下: 2.我们添加两个方法如下:
1 2 3 4 5 6 7 8 9 10 11 |
public class TestController : ApiController { public object Get1() { return "d1"; } public object Get2() { return "d2"; } } |
我们再用fiddler调试如下: 错误信息是: {"Message":"出现错误。","ExceptionMessage":"找到了与该请求匹配的多个操作: \r\n类型 Supernova.Webapi.Controllers.TestController 的 Get1\r\n类型 Supernova.Webapi.Controllers.TestController 的 Get2","ExceptionType":"System.InvalidOperationException","StackTrace":" 在 System.Web.Http.Controllers.ApiControllerActionSelector.ActionSelectorCacheItem.SelectAction(HttpControllerContext controllerContext)\r\n 在 System.Web.Http.ApiController.ExecuteAsync(HttpControllerContext controllerContext, CancellationToken cancellationToken)\r\n 在 System.Web.Http.Dispatcher.HttpControllerDispatcher.<SendAsync>d__1.MoveNext()"} 我们将代码改为如下:
1 2 3 4 5 6 7 8 9 10 11 12 |
public class TestController : ApiController { public object Get1() { return "d1"; } [HttpPost] public object Get2() { return "d2"; } } |
调试返回Get1的信息。 从上面两个测试我们可以得出如下结论: action的默认请求方式是HttpGet。 当多个action的 请求方式一样的话,在默认路由模板下(没有action),将会匹配多个操作。 基于上面两点结论,默认路由模板无法满足针对一种资源一种请求方式的多种操作(比如修改操作,可能针对不同的字段进行修改)。 定制模板路由 我们重新定制模板路由,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
using System; using System.Collections.Generic; using System.Linq; using System.Web.Http; namespace Supernova.Webapi { public static class WebApiConfig { public static void Register(HttpConfiguration config) { // Web API 配置和服务 // Web API 路由 config.MapHttpAttributeRoutes(); config.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{controller}/{action}/{id}", defaults: new { id = RouteParameter.Optional } ); } } } |
从上面我们可以看出,在默认路由的基础上,我们队路由模板增加了一级action。 测试api如下:
1 2 3 4 5 6 7 8 9 10 11 |
public class TestController : ApiController { public object Get1() { return "d1"; } public object Get2() { return "d2"; } } |
我们再通过http://192.168.0.230/api/test访问,返回404,如图: 我们通过http://192.168.0.230/api/test/Get1访问,结果正确,如图: 我们通过http://192.168.0.230/api/test/Get2访问,结果正确,如图: 通过定制路由模板我们可以得出如下结论: 通过在路由模板中增加action目录,对资源的定位直接作用到action上。 多个HttpGet方法可以共存于一个controller中。 基于上面两点结论,通过修改路由模板可以满足针对一种资源一种请求方式的多种操作。 2.2 特性路由 特性路由是通过给action打attribute的方式定义路由规则。 有时候我们会有这样的需求,我们请求的一个资源带有子资源。比如文章评论这样有关联关系的资源。我们希望通过如下URL获得某篇文章下的所有评论:api/book/id/comments。而仅仅凭借模板路由很难实现这种路由模式。这时候我们就需要特性路由来解决这个问题了。ASP.NET Web API为我们准备了Route特性,该特性可以直接打到Action上,使用非常灵活、直观。 下面我将先简单的介绍特性路由的使用方法。 我们重新定义api如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
public class TestController : ApiController { [Route("demo")] [HttpGet] public object Get1() { return "d1"; } [Route("demo/get")] [HttpGet] public object Get2() { return "d2"; } } |
[…]
View DetailsHttpWebRequest POST请求webapi:如果参数是简单类型,比如字符串(注意,拼接的字符串要HttpUtility.UrlEncode才行,否则服务端会丢失特殊字符&后面的数据) 要点:如下代码统一设置为:ContentType = "application/x-www-form-urlencoded"; 服务端代码1:URL格式为 POSTapi/Values public string Post([FromBody] string value) 则客户端Post的数据:拼接的字符串必须以 =开头,否则服务端无法取得value。例如:=rfwreewr2332322232 或者 {":value} 服务端代码2:URL格式为 POST api/Values?value={value} public string Post(string value) 则客户端Post的数据:需要url里拼接出KeyValue这样的数据 服务端代码3:URL格式为 POST api/Values public string Post() 则客户端Post的数据:无要求。例如:key=rfwreewr2332322232。 服务端:可以用HttpContext.Current.Request.InputStream或者HttpContext.Current.Request.Form[0]都可以获取 如果post的参数类型比较复杂,则需要自定义类 要点:如下代码统一设置为:ContentType = "application/json"; 服务端代码1:URL格式为 POST api/Values public string Post([FromBody] Model value)或者 public string Post(Model value) 则客户端Post的数据:{\"id\":\"test1\",\"name\":\"value\"}。服务端会自动映射到对象。 提交代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
HttpWebRequest wReq = (HttpWebRequest)WebRequest.Create("http://localhost:37831/api/Values"); wReq.Method = "Post"; //wReq.ContentType = "application/json"; //wReq.ContentType = "application/x-www-form-urlencoded"; wReq.ContentType = "application/json"; //byte[] data = Encoding.Default.GetBytes(HttpUtility.UrlEncode("key=rfwreewr2332322232&261=3&261=4")); byte[] data = Encoding.Default.GetBytes("{\"id\":\"test1\",\"name\":\"value\"}"); wReq.ContentLength = data.Length; Stream reqStream = wReq.GetRequestStream(); reqStream.Write(data, 0, data.Length); reqStream.Close(); using (StreamReader sr = new StreamReader(wReq.GetResponse().GetResponseStream())) { string result = sr.ReadToEnd(); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
// POST api/values //public string Post() //{ // FileInfo fi = new FileInfo(AppDomain.CurrentDomain.BaseDirectory + "log.txt"); // StreamWriter sw = fi.CreateText(); // StreamReader sr = new StreamReader(HttpContext.Current.Request.InputStream); // sw.WriteLine("1:" + sr.ReadToEnd()+"--"+ HttpContext.Current.Request.Form[0]); // sw.Flush(); // sw.Close(); // return "{\"test\":\"1\"}"; //} // POST api/values public HttpResponseMessage PostStudent(Student student) { FileInfo fi = new FileInfo(AppDomain.CurrentDomain.BaseDirectory + "log.txt"); StreamWriter sw = fi.AppendText(); sw.WriteLine("2:" + student.id); sw.Flush(); sw.Close(); HttpResponseMessage result = new HttpResponseMessage { Content = new StringContent("{\"test\":\"2\"}", Encoding.GetEncoding("UTF-8"), "application/json") }; return result; } |
这篇文章里有的方法也不错:http://www.cnblogs.com/rohelm/p/3207430.html from:http://blog.csdn.net/wyqlxy/article/details/49303345
View Details
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
using System; using System.Linq; using System.Web; using System.Web.Http; using System.Web.Security; namespace OtherApi.Auth { public class AuthFilterOutside : AuthorizeAttribute { //重写基类的验证方式,加入我们自定义的Ticket验证 public override void OnAuthorization(System.Web.Http.Controllers.HttpActionContext actionContext) { //url获取token var content = actionContext.Request.Properties["MS_HttpContext"] as HttpContextBase; var token = content.Request.Headers["Token"]; if (!string.IsNullOrEmpty(token)) { //解密用户ticket,并校验用户名密码是否匹配 if (ValidateTicket(token)) { base.IsAuthorized(actionContext); } else { HandleUnauthorizedRequest(actionContext); } } //如果取不到身份验证信息,并且不允许匿名访问,则返回未验证401 else { var attributes = actionContext.ActionDescriptor.GetCustomAttributes<AllowAnonymousAttribute>().OfType<AllowAnonymousAttribute>(); bool isAnonymous = attributes.Any(a => a is AllowAnonymousAttribute); if (isAnonymous) base.OnAuthorization(actionContext); else HandleUnauthorizedRequest(actionContext); } } //校验票据(数据库数据匹配) private bool ValidateTicket(string encryptToken) { bool flag = false; try { //获取数据库Token Dec.Models.TicketAuth model = Dec.BLL.TicketAuth.GetTicketAuthByToken(encryptToken); if (model.Token == encryptToken) //存在 { //未超时 flag = (DateTime.Now <= model.ExpireDate) ? true : false; } } catch (Exception ex) { } return flag; } } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 |
using System; using System.Web; using System.Web.Http; using System.Web.Security; using System.Net.Http; using System.Collections.Generic; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System.Text; using OtherApi.Auth; //引用验证 namespace SpiderApi.Controllers { /// <summary> /// 用户授权接口 /// </summary> public class AccountController : ApiController { #region 用户登录授权 /// <summary> /// 用户登录授权 /// </summary> /// <param name="username">用户名</param> /// <param name="password">密码</param> /// <returns></returns> [Route("api/account/login")] [HttpGet] public HttpResponseMessage Login(string username, string password) { //定义 ResponseResult obj = new ResponseResult(); var model = GetLoginModel(username, password); if (model != null) { int userId = model.UserId; string Token = UntilHelper.Md5Encode(UntilHelper.GetExtGuidID(), 32); var dtNow = DateTime.Now; #region 将身份信息保存票据表中,验证当前请求是否是有效请求 //判断此用户是否存在票据信息 if (Dec.BLL.TicketAuth.GetTicketAuthByUserId(userId) != null) { //清空重置 Dec.BLL.TicketAuth.DeleteByUserId(userId); } Dec.Models.TicketAuth ticket = new Dec.Models.TicketAuth(); ticket.UserID = userId; ticket.Token = Token; ticket.CreateDate = dtNow; ticket.ExpireDate = dtNow.AddMinutes(30); //30分钟过期 Dec.BLL.TicketAuth.Add(ticket); #endregion //返回信息 obj.status = true; obj.message = "用户登录成功"; JObject jo = new JObject(); jo.Add("userid", userId); jo.Add("loginname", model.LoginName); jo.Add("nickname", model.NickName); jo.Add("usertype", model.UserType); //(int)UserTypeEnum.Seller jo.Add("token", Token); obj.info = jo; } else { obj.status = false; obj.message = "用户登录失败"; } var resultObj = JsonConvert.SerializeObject(obj, Formatting.Indented); HttpResponseMessage result = new HttpResponseMessage { Content = new StringContent(resultObj, Encoding.GetEncoding("UTF-8"), "application/json") }; return result; } #endregion #region 用户退出登录,清空Token /// <summary> /// 用户退出登录,清空Token /// </summary> /// <param name="userId">用户ID</param> /// <returns></returns> [Route("api/account/loginout")] [HttpGet] public HttpResponseMessage LoginOut(int userId) { //定义 ResponseResult obj = new ResponseResult(); try { //清空数据库该用户票据数据 Dec.BLL.TicketAuth.DeleteByUserId(userId); } catch (Exception ex) { } //返回信息 obj.status = true; obj.message = "成功退出"; var resultObj = JsonConvert.SerializeObject(obj); HttpResponseMessage result = new HttpResponseMessage { Content = new StringContent(resultObj, Encoding.GetEncoding("UTF-8"), "application/json") }; return result; } #endregion #region 查询Token是否有效 /// <summary> /// 查询Token是否有效 /// </summary> /// <param name="token">token</param> /// <returns></returns> [Route("api/account/validatetoken")] [HttpGet] public HttpResponseMessage ValidateToken(string token) { //定义 ResponseResult obj = new ResponseResult(); bool flag = ValidateTicket(token); if (flag) { //返回信息 obj.status = true; obj.message = "token有效"; } else { obj.status = false; obj.message = "token无效"; } var resultObj = JsonConvert.SerializeObject(obj); HttpResponseMessage result = new HttpResponseMessage { Content = new StringContent(resultObj, Encoding.GetEncoding("UTF-8"), "application/json") }; return result; } #endregion #region 获取用户账户余额 /// <summary> /// 获取用户账户余额 /// </summary> /// <param name="userId">用户ID</param> /// <returns></returns> [Route("api/account/amount")] [HttpGet] [AuthFilterOutside] //添加验证 public HttpResponseMessage GetAmount(int userId) { //定义 ResponseResult obj = new ResponseResult(); //获取数据库数据 Dec.Models.UserInfo model = Dec.BLL.UserInfo.GetUserInfoByUserId(userId); if (model != null) { //返回信息 obj.status = true; obj.message = "获取用户账户余额成功"; JObject jo = new JObject(); jo.Add("userid", model.UserId); jo.Add("amount", model.Amount); obj.info = jo; } else { obj.status = false; obj.message = "获取用户账户余额失败"; } var resultObj = JsonConvert.SerializeObject(obj); HttpResponseMessage result = new HttpResponseMessage { Content = new StringContent(resultObj, Encoding.GetEncoding("UTF-8"), "application/json") }; return result; } #endregion /// <summary> /// 用户充值接口 /// </summary> /// <param name="userid">用户ID</param> /// <param name="amount">充值金额</param> /// <returns></returns> [Route("api/account/recharge")] [HttpGet] [AuthFilterInside] public HttpResponseMessage Recharge(string userid, double amount) { //定义 ResponseResult obj = new ResponseResult(); //获取数据库数据 //返回信息 obj.status = true; obj.message = "操作成功,请等待第三方支付平台返回通知核实是否到账"; JObject jo = new JObject(); jo.Add("userid", "123456789"); jo.Add("amount", 125.80); obj.info = jo; var resultObj = JsonConvert.SerializeObject(obj); HttpResponseMessage result = new HttpResponseMessage { Content = new StringContent(resultObj, Encoding.GetEncoding("UTF-8"), "application/json") }; return result; } #region 验证票据是否有效 /// <summary> /// 验证票据是否有效 /// </summary> /// <param name="encryptToken">token</param> /// <returns></returns> private bool ValidateTicket(string encryptToken) { bool flag = false; try { //获取数据库Token Dec.Models.TicketAuth model = Dec.BLL.TicketAuth.GetTicketAuthByToken(encryptToken); if (model.Token == encryptToken) //存在 { //未超时 flag = (DateTime.Now <= model.ExpireDate) ? true : false; } } catch (Exception ex) { } return flag; } #endregion #region 用户登录 /// <summary> /// 用户登录 /// </summary> /// <param name="userName">用户名</param> /// <param name="userPwd">密码</param> /// <returns></returns> private Dec.Models.UserInfo GetLoginModel(string userName, string userPwd) { Dec.Models.UserInfo model = new Dec.Models.UserInfo(); try { if (!string.IsNullOrWhiteSpace(userName) && !string.IsNullOrWhiteSpace(userPwd)) { //数据库比对 model = Dec.BLL.UserInfo.GetUserInfoByUserNamePwd(userName, UntilHelper.Md5Encode(userPwd, 32)); } } catch (Exception ex) { } return model; } #endregion } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
////////////////////////////////////////////////////////////////// using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Http; using System.Web.Mvc; using System.Web.Routing; namespace SpiderApi { public class WebApiApplication : System.Web.HttpApplication { protected void Application_Start() { //WebApi文档 AreaRegistration.RegisterAllAreas(); GlobalConfiguration.Configure(WebApiConfig.Register); } protected void Application_PostAuthorizeRequest() { //Enable Session HttpContext.Current.SetSessionStateBehavior(System.Web.SessionState.SessionStateBehavior.Required); } } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 |
// Uncomment the following to provide samples for PageResult<T>. Must also add the Microsoft.AspNet.WebApi.OData // package to your project. 先安装Help Page包 HelpPage=>App_start=>HelpPageConfig.cs ////#define Handle_PageResultOfT using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Net.Http.Headers; using System.Reflection; using System.Web; using System.Web.Http; using SpiderApi.Models; #if Handle_PageResultOfT using System.Web.Http.OData; #endif namespace SpiderApi.Areas.HelpPage { /// <summary> /// Use this class to customize the Help Page. /// For example you can set a custom <see cref="System.Web.Http.Description.IDocumentationProvider"/> to supply the documentation /// or you can provide the samples for the requests/responses. /// </summary> public static class HelpPageConfig { [SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "SpiderApi.Areas.HelpPage.TextSample.#ctor(System.String)", Justification = "End users may choose to merge this string with existing localized resources.")] [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "bsonspec", Justification = "Part of a URI.")] public static void Register(HttpConfiguration config) { //// Uncomment the following to use the documentation from XML documentation file. //开启解析 config.SetDocumentationProvider(new XmlDocumentationProvider(HttpContext.Current.Server.MapPath("~/Bin/SpiderApi.XML"))); //// Uncomment the following to use "sample string" as the sample for all actions that have string as the body parameter or return type. //// Also, the string arrays will be used for IEnumerable<string>. The sample objects will be serialized into different media type //// formats by the available formatters. //config.SetSampleObjects(new Dictionary<Type, object> //{ // {typeof(string), "sample string"}, // {typeof(IEnumerable<string>), new string[]{"sample 1", "sample 2"}} //}); //添加映射 config.SetSampleResponse(Sample.BatchSendMessageResponse(), new MediaTypeHeaderValue("text/json"), "MessageQueue", "BatchSendMessage"); config.SetSampleResponse(Sample.BatchReceiveMessageResponse(), new MediaTypeHeaderValue("text/json"), "MessageQueue", "BatchReceiveMessage"); config.SetSampleResponse(Sample.DeleteMessageResponse(), new MediaTypeHeaderValue("text/json"), "MessageQueue", "DeleteMessage"); config.SetSampleResponse(Sample.BatchDeleteMessageResponse(), new MediaTypeHeaderValue("text/json"), "MessageQueue", "BatchDeleteMessage"); config.SetSampleResponse(Sample.ChangeMessageVisibilityResponse(), new MediaTypeHeaderValue("text/json"), "MessageQueue", "ChangeMessageVisibility"); // Extend the following to provide factories for types not handled automatically (those lacking parameterless // constructors) or for which you prefer to use non-default property values. Line below provides a fallback // since automatic handling will fail and GeneratePageResult handles only a single type. #if Handle_PageResultOfT config.GetHelpPageSampleGenerator().SampleObjectFactories.Add(GeneratePageResult); #endif // Extend the following to use a preset object directly as the sample for all actions that support a media // type, regardless of the body parameter or return type. The lines below avoid display of binary content. // The BsonMediaTypeFormatter (if available) is not used to serialize the TextSample object. config.SetSampleForMediaType( new TextSample("Binary JSON content. See http://bsonspec.org for details."), new MediaTypeHeaderValue("application/bson")); //// Uncomment the following to use "[0]=foo&[1]=bar" directly as the sample for all actions that support form URL encoded format //// and have IEnumerable<string> as the body parameter or return type. //config.SetSampleForType("[0]=foo&[1]=bar", new MediaTypeHeaderValue("application/x-www-form-urlencoded"), typeof(IEnumerable<string>)); //// Uncomment the following to use "1234" directly as the request sample for media type "text/plain" on the controller named "Values" //// and action named "Put". //config.SetSampleRequest("1234", new MediaTypeHeaderValue("text/plain"), "Values", "Put"); //// Uncomment the following to use the image on "../images/aspNetHome.png" directly as the response sample for media type "image/png" //// on the controller named "Values" and action named "Get" with parameter "id". //config.SetSampleResponse(new ImageSample("../images/aspNetHome.png"), new MediaTypeHeaderValue("image/png"), "Values", "Get", "id"); //// Uncomment the following to correct the sample request when the action expects an HttpRequestMessage with ObjectContent<string>. //// The sample will be generated as if the controller named "Values" and action named "Get" were having string as the body parameter. //config.SetActualRequestType(typeof(string), "Values", "Get"); //// Uncomment the following to correct the sample response when the action returns an HttpResponseMessage with ObjectContent<string>. //// The sample will be generated as if the controller named "Values" and action named "Post" were returning a string. //config.SetActualResponseType(typeof(string), "Values", "Post"); } #if Handle_PageResultOfT private static object GeneratePageResult(HelpPageSampleGenerator sampleGenerator, Type type) { if (type.IsGenericType) { Type openGenericType = type.GetGenericTypeDefinition(); if (openGenericType == typeof(PageResult<>)) { // Get the T in PageResult<T> Type[] typeParameters = type.GetGenericArguments(); Debug.Assert(typeParameters.Length == 1); // Create an enumeration to pass as the first parameter to the PageResult<T> constuctor Type itemsType = typeof(List<>).MakeGenericType(typeParameters); object items = sampleGenerator.GetSampleObject(itemsType); // Fill in the other information needed to invoke the PageResult<T> constuctor Type[] parameterTypes = new Type[] { itemsType, typeof(Uri), typeof(long?), }; object[] parameters = new object[] { items, null, (long)ObjectGenerator.DefaultCollectionSize, }; // Call PageResult(IEnumerable<T> items, Uri nextPageLink, long? count) constructor ConstructorInfo constructor = type.GetConstructor(parameterTypes); return constructor.Invoke(parameters); } } return null; } #endif } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
/* API接口测试工具 - WebApiTestClient使用--Nuget引入组件 --A Simple Test Client for ASP.NET Web API */ /* 1、修改Api.cshtml文件 通过上述步骤,就能将组件WebAPITestClient引入进来。下面我们只需要做一件事:打开文件 (根据 Areas\HelpPage\Views\Help) Api.cshtml 并添加以下内容: @Html.DisplayForModel("TestClientDialogs") @Html.DisplayForModel("TestClientReferences") 添加后Api.cshtml文件的代码如下 */ @using System.Web.Http @using WebApiTestClient.Areas.HelpPage.Models @model HelpPageApiModel @{ var description = Model.ApiDescription; ViewBag.Title = description.HttpMethod.Method + " " + description.RelativePath; } <link type="text/css" href="~/Areas/HelpPage/HelpPage.css" rel="stylesheet" /> <div id="body" class="help-page"> <section class="featured"> <div class="content-wrapper"> <p> @Html.ActionLink("Help Page Home", "Index") </p> </div> </section> <section class="content-wrapper main-content clear-fix"> @Html.DisplayForModel() </section> </div> @Html.DisplayForModel("TestClientDialogs") @section Scripts{ <link href="~/Areas/HelpPage/HelpPage.css" rel="stylesheet" /> @Html.DisplayForModel("TestClientReferences") } |
from:http://blog.csdn.net/smartsmile2012/article/details/52936011
View Details