HttpClient client = new HttpClient(); client.BaseAddress = new Uri("http://localhost:56851/"); // Add an Accept header for JSON format. client.DefaultRequestHeaders.Accept.Add( new MediaTypeWithQualityHeaderValue("application/json")); HttpResponseMessage response = client.GetAsync("api/User").Result; if (response.IsSuccessStatusCode) { var users = response.Content.ReadAsAsync& lt;IEnumerable<Users>>().Result; usergrid.ItemsSource = users; } else { MessageBox.Show("Error Code" + response.StatusCode + " : Message - " + response.ReasonPhrase); }
Но как же это муторно каждый раз лезть в описание контроллеров, проверять типы, короче хочется вот так:
var resp = GetResponse<SomeController>(c => gc.SomeAction(new Dto{val = "123"}));
Как выяснилось, это вполне можно реализовать применив немного уличной магии деревья выражений
Получение информации об API
для начала нам нужно знать какое API вообще есть, для этого замапим роуты
[SetUp] public void SetUp() { _cfg = new HttpConfiguration(); _cfg.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{controller}/{action}/{id}", defaults: new { id = RouteParameter.Optional } ); }
Вызов удаленного метода
ApiDescriptions теперь знает о том, где искать контроллеры и любезно предоставит метаинформацию. В WebApi может быть много вариантов вызова одного метода: я никогда не использую два Http-метода для одного метода API, поэтому этот кейс меня не волнует. С чистой совестью возьмем первый подходящий метод
protected HttpResponseMessage GetResponse<T>(Expression<Action<T>> expression) where T : ApiController { var baseAddress = System.Configuration.ConfigurationManager.AppSettings["BaseAddress"]; var convert = (MethodCallExpression)expression.Body; var name = convert.Method.Name; var paramsName = convert.Method.GetParameters().Select(p => p.Name).ToArray(); var desc = _cfg.Services.GetApiExplorer().ApiDescriptions.First( d => d.ActionDescriptor.ControllerDescriptor.ControllerType == typeof(T) && d.ActionDescriptor.ActionName == name); //...
Допущение №2. Кроме JSON мне ничего не интересно. Для get-методов и post-с примитивами в параметрах заменим вхождения вида paramName={paramName} на paramName=значение из Expression, которое мы передали.
using (var client = new HttpClient { BaseAddress = new Uri(baseAddress) }) { client.DefaultRequestHeaders.Accept.Add( new MediaTypeWithQualityHeaderValue("application/json")); var relPath = desc.RelativePath; var index = 0; if (relPath.Contains("?")) { foreach (var pars in paramsName) { relPath = relPath.Replace( string.Format("{{{0}}}", pars.Name), InvokeExpression(convert.Arguments[index++], pars.ParameterType).Return(o => o.ToString(), string.Empty)); } }
InvokeExpression
Самый простой способ получить значение любого Expression — скомпилировать его в лямбду, что я и сделал. Откровенно говоря, мы знаем возвращаемый тип на этапе компиляции из типа контроллера. Но в этом случае придется делать отдельный кейс для методов, возвращающих void. В этом случае придется использовать Action вместо Func<T,TResult>. Такой код уже достаточно сложен для понимания. За производительностью я не гонюсь, — сетевые издержки сожрут все те наносекунды, которые будут сэкономлены на компиляции.
private static object InvokeExpression(Expression e, Type returnType) { return Expression.Lambda( typeof (Func<>).MakeGenericType(returnType), e).Compile().DynamicInvoke(); }
Получение результата
Осталось самое простое — получить результат и вызвать метод. Для Post методов считаем, что всегда есть родительский объект-wrapper. Возвращаем результат или падаем с ошибкой.
var uri = new Uri(new Uri(baseAddress), relPath); var resp = desc.HttpMethod.Method == HttpMethod.Post.ToString() ? client.PostAsJsonAsync(uri.ToString(), InvokeExpression(convert.Arguments.Single(), desc.ParameterDescriptions.Single().ParameterDescriptor.ParameterType)).Result : client.GetAsync(uri).Result; if (resp.StatusCode == HttpStatusCode.InternalServerError) { using (var sr = new StreamReader(resp.Content.ReadAsStreamAsync().Result)) { throw new InvalidOperationException(sr.ReadToEnd()); } } return resp;
В итоге
Получился вот такой метод
protected HttpResponseMessage GetResponse<T>(Expression<Action<T>> expression) where T : ApiController { var baseAddress = System.Configuration.ConfigurationManager.AppSettings["BaseAddress"]; var convert = (MethodCallExpression)expression.Body; var name = convert.Method.Name; var paramsName = convert.Method.GetParameters().ToArray(); var desc = _cfg.Services.GetApiExplorer().ApiDescriptions.First( d => d.ActionDescriptor.ControllerDescriptor.ControllerType == typeof(T) && d.ActionDescriptor.ActionName == name); using (var client = new HttpClient { BaseAddress = new Uri(baseAddress) }) { client.DefaultRequestHeaders.Accept.Add( new MediaTypeWithQualityHeaderValue("application/json")); var relPath = desc.RelativePath; var index = 0; if (relPath.Contains("?")) foreach (var pars in paramsName) { relPath = relPath.Replace( string.Format("{{{0}}}", pars.Name), InvokeExpression(convert.Arguments[index++], pars.ParameterType).Return(o => o.ToString(), string.Empty)); } var uri = new Uri(new Uri(baseAddress), relPath); var resp = desc.HttpMethod.Method == HttpMethod.Post.ToString() ? client.PostAsJsonAsync(uri.ToString(), InvokeExpression(convert.Arguments.Single(), desc.ParameterDescriptions.Single().ParameterDescriptor.ParameterType)).Result : client.GetAsync(uri).Result; if (resp.StatusCode == HttpStatusCode.InternalServerError) { using (var sr = new StreamReader(resp.Content.ReadAsStreamAsync().Result)) { throw new InvalidOperationException(sr.ReadToEnd()); } } return resp; } }
Много чего в этом коде не идеально, но свою цель он выполнят, теперь я могу писать такие тесты:
[Test] public void UserController_TokenValid_WrongTokenReturnFalse() { var resp = GetResponse<UserController>(gc => gc.TokenValid("123")); Assert.AreEqual(false, resp.Content.ReadAsAsync<bool>().Result); }
Или более сложные, например такие:
var obj = new RoundResultDtoIn() { LevelId = 3, RoomName = "123", RoundTime = 50, StartDateTime = DateTime.Now }; GetResponse<GameController>(gc => gc.SaveResults(obj));
ссылка на оригинал статьи http://habrahabr.ru/post/202044/
Добавить комментарий