Asp.Net实现WebApi跨域 (非MVC)
目前WebApi在使用上大部分都是跟MVC组合的,而且使用起来也确实十分便利。
但有时候我们也需要在WebForm中使用WebApi,二者还是有一定区别的。
首先看下结构
①ApiController
即Controller部分,当然也包含Api的路径也是很重要的。这里面是Api/{function}/{controller}/{action}
看一下Controller里面的内容
using System; using System.Web.Http; namespace WebApiTest.Api.Func { public class TestController : ApiController { [HttpPost] public TestModel Post([FromBody]TestModel model) { model.Name = "Post"; model.UpdatedOn = DateTime.Now; model.Age++; return model; } } }
注意的地方有这么几点:
- Controller要继承ApiController
- Action要加上特性 [HttpPost] ,[HttpGet] 。。。如下图
- 一般情况我们用的都是[HttpPost],这时候参数中就要加特性[FromBody]了
- 为了能够直接使用请求过来的数据,而不必去转化成对应的实体类,我们需要给参数实体类加特性[Newtonsoft.Json.JsonObject]否则返回的数据会是酱紫的
这是使用的实体类
[Newtonsoft.Json.JsonObject] public class TestModel { public string Name { set; get; } public int Age { set; get; } public DateTime UpdatedOn { set; get; } }
②跨域处理程序CorsHandler.cs
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Web; 5 using System.Net.Http; 6 using System.Threading.Tasks; 7 using System.Threading; 8 using System.Net; 9 10 namespace WebApiTest.Handler 11 { 12 public class CorsHandler : DelegatingHandler 13 { 14 const string Origin = "Origin"; 15 const string AccessControlRequestMethod = "Access-Control-Request-Method"; 16 const string AccessControlRequestHeaders = "Access-Control-Request-Headers"; 17 const string AccessControlAllowOrigin = "Access-Control-Allow-Origin"; 18 const string AccessControlAllowMethods = "Access-Control-Allow-Methods"; 19 const string AccessControlAllowHeaders = "Access-Control-Allow-Headers"; 20 21 protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) 22 { 23 bool isCorsRequest = request.Headers.Contains(Origin); 24 bool isPreflightRequest = request.Method == HttpMethod.Options; 25 if (isCorsRequest) 26 { 27 if (isPreflightRequest) 28 { 29 return Task.Factory.StartNew<HttpResponseMessage>(() => 30 { 31 HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.OK); 32 response.Headers.Add(AccessControlAllowOrigin, request.Headers.GetValues(Origin).First()); 33 34 string accessControlRequestMethod = request.Headers.GetValues(AccessControlRequestMethod).FirstOrDefault(); 35 if (accessControlRequestMethod != null) 36 { 37 response.Headers.Add(AccessControlAllowMethods, accessControlRequestMethod); 38 } 39 40 string requestedHeaders = string.Join(", ", request.Headers.GetValues(AccessControlRequestHeaders)); 41 if (!string.IsNullOrEmpty(requestedHeaders)) 42 { 43 response.Headers.Add(AccessControlAllowHeaders, requestedHeaders); 44 } 45 46 return response; 47 }, cancellationToken); 48 } 49 else 50 { 51 return base.SendAsync(request, cancellationToken).ContinueWith<HttpResponseMessage>(t => 52 { 53 HttpResponseMessage resp = t.Result; 54 resp.Headers.Add(AccessControlAllowOrigin, request.Headers.GetValues(Origin).First()); 55 return resp; 56 }); 57 } 58 } 59 else 60 { 61 return base.SendAsync(request, cancellationToken); 62 } 63 } 64 } 65 }
③Controller配置程序 HttpControllerSelector.cs
1 using System.Collections.Generic; 2 using System.Linq; 3 using System.Net.Http; 4 using System.Reflection; 5 using System.Web.Http; 6 using System.Web.Http.Controllers; 7 using System.Web.Http.Dispatcher; 8 9 namespace WebApiTest.Handler 10 { 11 public class HttpControllerSelector : DefaultHttpControllerSelector 12 { 13 private HttpConfiguration configuration = null; 14 public HttpControllerSelector(HttpConfiguration configuration) 15 : base(configuration) 16 { 17 this.configuration = configuration; 18 GetControllerMapping(); 19 } 20 21 public override string GetControllerName(HttpRequestMessage request) 22 { 23 object function, c; 24 var routedata = request.GetRouteData(); 25 26 if (routedata.Values.TryGetValue("function", out function) && 27 routedata.Values.TryGetValue("controller", out c)) 28 { 29 var item = dict.FirstOrDefault(t => t.Key.Contains(string.Format("{0}.{1}controller", function, c).ToLower())); 30 if (item.Value != null) 31 { 32 return item.Value.ControllerName; 33 } 34 } 35 return base.GetControllerName(request); 36 } 37 38 IDictionary<string, HttpControllerDescriptor> dict = new Dictionary<string, HttpControllerDescriptor>(); 39 public override IDictionary<string, HttpControllerDescriptor> GetControllerMapping() 40 { 41 var list = Assembly.GetAssembly(this.GetType()).GetTypes().Where(t => t.IsSubclassOf(typeof(ApiController))); 42 foreach (var type in list) 43 { 44 dict.Add(type.FullName.ToLower(), new HttpControllerDescriptor(this.configuration, type.FullName.ToLower(), type)); 45 } 46 47 return dict; 48 } 49 50 public override HttpControllerDescriptor SelectController(HttpRequestMessage request) 51 { 52 HttpControllerDescriptor c; 53 var cn = GetControllerName(request); 54 if (dict.TryGetValue(cn, out c)) 55 { 56 return c; 57 } 58 return base.SelectController(request); 59 } 60 } 61 }
④路由注册程序WebApiConfig.cs
1 using System.Web.Http; 2 using System.Web.Http.Dispatcher; 3 4 namespace WebApiTest.Handler 5 { 6 public static class WebApiConfig 7 { 8 public static void Register(HttpConfiguration config) 9 { 10 // Web API 配置和服务 11 config.Services.Replace(typeof(IHttpControllerSelector), new HttpControllerSelector(config)); 12 13 config.Routes.MapHttpRoute( 14 name: "Api", 15 routeTemplate: "api/{function}/{controller}/{action}" 16 ); 17 } 18 } 19 }
⑤Global.asax.cs
在该文件中添加如下代码
public override void Init() { this.EndRequest += Global_EndRequest; this.BeginRequest += Global_BeginRequest; base.Init(); } protected void Application_Start(object sender, EventArgs e) { GlobalConfiguration.Configuration.MessageHandlers.Add(new CorsHandler()); WebApiConfig.Register(GlobalConfiguration.Configuration); }
至此 服务端的配置就基本OK了。
调用的地方用如下Ajax就可以了
$.ajax({ url: "api/func/Test/Post", type: "POST", data: {Name:"Ray"}, dataType:"json", success: function (result) { console.log(result); } });
上面讲的是服务端的配置,顺带一提客户端的调用。
但是,如果是在后台调用某个WebApi该如何破呢?
我使用的是Intersoft的CrossLight,用起来也是比较简单。
TestModel model = new TestModel(); RestClient c = new RestClient("http://localhost:1234/API/Module/"); RestRequest req = new RestRequest("Function/Do", HttpMethod.POST); req.RequestFormat = RequestDataFormat.Json; req.AddBody(model); var res = c.ExecuteAsync<WebApiModel>(req); var tmp = JsonConvert.DeserializeObject<WebApiModel<TestModel>>(res.Result.Content).Model; if (res.Result.Data.HasError) { throw new Exception(res.Result.Data.ErrorMessage); }
这里面有个包装的类WebApiModel,是为了更好的传递其他信息(如错误信息),定义如下
using System; namespace XX { [Newtonsoft.Json.JsonObject] [Serializable] public class WebApiModel { public WebApiModel() { } public WebApiModel(object model) { this.Model = model; } public object Model { get; set; } public bool HasError { get { return !string.IsNullOrEmpty(ErrorMessage); } } public string ErrorMessage { get; set; } } }
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace XX { [Newtonsoft.Json.JsonObject] [Serializable] public class WebApiModel<T> { public WebApiModel() { } public WebApiModel(T model) { this.Model = model; } public T Model { get; set; } public bool HasError { get { return !string.IsNullOrEmpty(ErrorMessage); } } public string ErrorMessage { get; set; } } }