I'm making an app that'll host database and communicate with web site and wpf app via Web API. So, the problem is this: how can I make GET request with string parameter? I already did similar thing, but with integer parameter. Almost the same code does not work for string parameter. Why?
Code that works with int param:
Controller method:
[HttpGet]
[ActionName("GetResItems")]
public IHttpActionResult GetResItems(int id)
{
using (var db = new FoodOrderingContext())
{
List<ItemsInRestaurant> list = db.ItemsInRestaurants.Where(x => x.ItemId == id).ToList();
List<ItemsInRestaurantVM> toSend = new List<ItemsInRestaurantVM>();
foreach (var item in list)
{
toSend.Add(new ItemsInRestaurantVM(item));
}
if (toSend.Count == 0)
{
return NotFound();
}
return Ok(toSend);
}
}
Code on the client WPF side:
public List<ItemsInRestaurantVM> GetResItems(int id)
{
List<ItemsInRestaurantVM> list = null;
using (var client = new HttpClient())
{
client.BaseAddress = new Uri("http://localhost:52407/");
var responseTask = client.GetAsync("api/item/getresitems/" + id);
responseTask.Wait();
var result = responseTask.Result;
if (result.IsSuccessStatusCode)
{
var readingTask = result.Content.ReadAsAsync<List<ItemsInRestaurantVM>>();
readingTask.Wait();
list = readingTask.Result;
}
}
return list;
}
Now, the code that does NOT work:
Server side:
[HttpGet]
[ActionName("GetTop10Items")]
public IHttpActionResult GetTop10Items(string type)
{
using (var db = new FoodOrderingContext())
{
List<Top10ItemsVM> list = new List<Top10ItemsVM>();
foreach (var item in db.Items.ToList())
{
if (item.Type.Equals(type))
{
Top10ItemsVM toAdd = new Top10ItemsVM();
toAdd.Name = item.Name;
toAdd.Price = item.Price.ToString() + " $";
toAdd.Ammount = db.OrderItems.Where(x => x.ItemId == item.ItemId).Select(x => x.Ammount).Sum().ToString();
toAdd.Total = (db.OrderItems.Where(x => x.ItemId == item.ItemId).Select(x => x.Ammount).Sum() * item.Price).ToString() + " $";
list.Add(toAdd);
}
}
if (list.Count > 0)
{
return Ok(list);
}
else
{
return NotFound();
}
}
Client side:
public List<Top10ItemsVM> GetTop10Items(string type)
{
List<Top10ItemsVM> list = null;
using (var client = new HttpClient())
{
client.BaseAddress = new Uri("http://localhost:52407/");
var responseTask = client.GetAsync("api/item/GetTop10Items/" + type);
responseTask.Wait();
var result = responseTask.Result;
if (result.IsSuccessStatusCode)
{
var readTask = result.Content.ReadAsAsync<List<Top10ItemsVM>>();
readTask.Wait();
list = readTask.Result.OrderByDescending(x => x.Ammount).Take(10).ToList();
}
return list;
}
}
First part works like it should be. The second one does not even call the server method. You can clearly see it's almost the same code. What am I missing here?
Route parameter are transmitted via ?param=value. Your first case only works because the asp.net template adds a default route map which looks something like this:
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
Which defines that every Action method which has a parameter id defined, maps automatically to: myurl/action/<id>.
To make your second case work you have to call the action like this:
api/item/GetTop10Items?type=something
where you explicitly define the route parameter type and its value.
Related
I am currently working on an ASP.NET project in .NET Framework and I want to build an integration test calling my API.
More precisely, I want to create an HTTPClient, that I can pass to my proxy which will then allow me to make so calls on my controller. I expect that my GET method of my controller is reached and that I get in return my object AssignmentDTO. I wrote the test below to test the attribute Description of that object.
I have to precise that I need to pass an Authorization. The one used in my company is OAuth2, but from Postman I used a basic Authorization and it works fine. So I do it by adding a value in the default header : client.DefaultRequestHeaders.Add("Authorization", "Basic xxxxxxx");
Here is my Get method in my controller :
[ServiceAspect(Access.CanAccessTheApplication)]
[SwaggerResponse(HttpStatusCode.OK, typeof(AssignmentDTO), Description ="The TrainingRegistration name.")]
[SwaggerResponse(HttpStatusCode.NotFound, typeof(void), Description = "No data found.")]
[SwaggerResponse(HttpStatusCode.BadRequest, typeof(IEnumerable<Message>))]
[Route("")]
[ActionName("GetAll")]
public IHttpActionResult Get()
{
var dto = new AssignmentDTO()
{
Description = "blabla",
ActivityId = 5,
Id = 10
};
return Ok(dto);
}
I found many things on the web, and some suggest to use OWIN to create a self-host API. This is what I use here.
[TestMethod]
public async Task T004_Get_CallThroughProxy()
{
//create client here
HttpClient client = null;
string baseAddress = "http://localhost:44305/";
using (WebApp.Start<Startup>(url: baseAddress))
{
// Create HttpClient and make a request to api/values
client = new HttpClient();
//Act
client.DefaultRequestHeaders.Add("Authorization", $"Basic {credential}");
var proxy = new TNPCoreInterfaceClient(client) { BaseUrl = "http://" };
//Assert
var result = await proxy.TrainingRegistration_GetAllAsync();
Assert.AreEqual("blabla", result.Description);
}
}
Where the startup class is :
public class Startup
{
// This code configures Web API. The Startup class is specified as a type
// parameter in the WebApp.Start method.
public void Configuration(IAppBuilder appBuilder)
{
// Configure Web API for self-host.
HttpConfiguration config = new HttpConfiguration();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
//changed UseWebApi to use
appBuilder.UseWebApi(config);
}
}
the TNPCoreInterfaceClient is an auto generated proxy that shouldn't be a source of problem :
public async System.Threading.Tasks.Task<AssignmentDTO> TrainingRegistration_GetAllAsync(System.Threading.CancellationToken cancellationToken)
{
var urlBuilder_ = new System.Text.StringBuilder();
urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/interface/trainingregistration/v1");
var client_ = _httpClient;
var disposeClient_ = false;
try
{
using (var request_ = new System.Net.Http.HttpRequestMessage())
{
request_.Method = new System.Net.Http.HttpMethod("GET");
request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json"));
PrepareRequest(client_, request_, urlBuilder_);
var url_ = urlBuilder_.ToString();
request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute);
PrepareRequest(client_, request_, url_);
var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
var disposeResponse_ = true;
try
{
var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value);
if (response_.Content != null && response_.Content.Headers != null)
{
foreach (var item_ in response_.Content.Headers)
headers_[item_.Key] = item_.Value;
}
ProcessResponse(client_, response_);
var status_ = (int)response_.StatusCode;
if (status_ == 200)
{
var objectResponse_ = await ReadObjectResponseAsync<AssignmentDTO>(response_, headers_).ConfigureAwait(false);
if (objectResponse_.Object == null)
{
throw new RestException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null);
}
return objectResponse_.Object;
}
else
if (status_ == 404)
{
string responseText_ = ( response_.Content == null ) ? string.Empty : await response_.Content.ReadAsStringAsync().ConfigureAwait(false);
throw new RestException("No data found.", status_, responseText_, headers_, null);
}
else
if (status_ == 400)
{
var objectResponse_ = await ReadObjectResponseAsync<System.Collections.ObjectModel.ObservableCollection<Message>>(response_, headers_).ConfigureAwait(false);
if (objectResponse_.Object == null)
{
throw new RestException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null);
}
throw new RestException<System.Collections.ObjectModel.ObservableCollection<Message>>("A server side error occurred.", status_, objectResponse_.Text, headers_, objectResponse_.Object, null);
}
else
{
var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false);
throw new RestException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null);
}
}
finally
{
if (disposeResponse_)
response_.Dispose();
}
}
}
finally
{
if (disposeClient_)
client_.Dispose();
}
}
When I run the project, the "manual" test with Postman. I call the url https://localhost:44305/interface/trainingregistration/v1 with Postman and with the specified Auth and I got the right DTO in return.
When I run the test in debug, It crashes on client_.SendAsync(... in TrainingRegistration_GetAllAsync and I get the error message :
System.InvalidOperationException: 'An invalid request URI was provided. The request URI must either be an absolute URI or BaseAddress must be set.'
EDIT (Jan 27 2021) :
For those wondering, I never succeeded doing it at that time.
The next api returns in Postmen and to the client item1,item2
While I am using ValueTuple to change the names (the names not so important, but I can’t return item1,item2)
public async Task<(List<CategoryFilterResponseDTO> categoryFilters, string MetaDataDescription)> GetCategoryFilterPage([FromBody]categoryFilterRequestDTO categoryFilterRequest)
{
var logItem = new LogDTO();
var result = await _service.GetCategoryFilterPage(categoryFilterRequest);
try
{
OnStart(logItem, new object[] { categoryFilterRequest });
var categoryFilters = result.categoryFilters;
var MetaDataDescription = result.MetaDataDescription;
return (categoryFilters: categoryFilters, MetaDataDescription: MetaDataDescription);
}
}
the method:
public async Task<(List<CategoryFilterResponseDTO> categoryFilters, string MetaDataDescription)> GetCategoryFilterPage(categoryFilterRequestDTO categoryFilterRequestDTO)
{
List<CategoryFilterResponseDTO> categoryFilter = new List<CategoryFilterResponseDTO>();
List<FavoriteDTO> isFavorite = null;
string MetaDataDescription = "";
(List<FilterSortDTO<FlatSearchCategory>>, int) searchCategory = await _clubRepo.CategoryFilterPage(categoryFilterRequestDTO);//BranchesCount
if (searchCategory.Item2 == 0)
{
MetaDataDescription = GetCategoryDetails(categoryFilterRequestDTO.CategoryFirstFatherID.Value).CategoryName;
return (categoryFilters: categoryFilter, MetaDataDescription: MetaDataDescription);
}
You can change your return to the following:
return Ok(new
{
categoryFilters = categoryFilter,
metaDataDescription = MetaDataDescription
});
You will need to change your return type to ActionResult or similar as well.
Because that's the name of the fields on a ValueTuple<,>.
The names given to the values are only available to source code.
I'm getting multiple errors. As I'm new to this async/await process. So with little research I've done this bit:-
I've a function like:-
public async Task<JsonResult> GetMultipleTblResult(AdminBundle aBundleFetch)
{
if (!string.IsNullOrEmpty(aBundleFetch.ListType) && aBundleFetch.ListType.Equals(Constants.Board))
{
ArrayList MainList = new ArrayList();
aBundleFetch.ListType = Constants.Board;
Func<ArrayList> functionBoard = new Func<ArrayList>(() => FetchTableDataAsync(aBundleFetch)); // Getting Error (Cannot implicitly convert type 'System.Threading.Tasks.Task<System.Collections.Generic.IEnumerable<System.Collections.ArrayList>>' to 'System.Collections.ArrayList')
ArrayList resBoard = await Task.Factory.StartNew<ArrayList>(functionBoard);
aBundleFetch.ListType = Constants.Classes;
Func<ArrayList> functionClass = new Func<ArrayList>(() => FetchTableDataAsync(aBundleFetch)); // Getting Error (Cannot implicitly convert type 'System.Threading.Tasks.Task<System.Collections.Generic.IEnumerable<System.Collections.ArrayList>>' to 'System.Collections.ArrayList')
ArrayList resClass = await Task.Factory.StartNew<ArrayList>(functionClass);
aBundleFetch.ListType = Constants.ClassSubject;
Func<ArrayList> functionClassSubject = new Func<ArrayList>(() => FetchTableDataAsync(aBundleFetch)); // Getting Error (Cannot implicitly convert type 'System.Threading.Tasks.Task<System.Collections.Generic.IEnumerable<System.Collections.ArrayList>>' to 'System.Collections.ArrayList')
ArrayList resClassSubject = await Task.Factory.StartNew<ArrayList>(functionClassSubject);
aBundleFetch.ListType = Constants.ClassMaterial;
Func<ArrayList> functionClassMaterial = new Func<ArrayList>(() => FetchTableDataAsync(aBundleFetch)); // Getting Error (Cannot implicitly convert type 'System.Threading.Tasks.Task<System.Collections.Generic.IEnumerable<System.Collections.ArrayList>>' to 'System.Collections.ArrayList')
ArrayList resClassMaterial = await Task.Factory.StartNew<ArrayList>(functionClassMaterial);
MainList.Add(resBoard);
MainList.Add(resClass);
MainList.Add(resClassSubject);
MainList.Add(resClassMaterial);
var jsonSerialiser = new JavaScriptSerializer();
var json = jsonSerialiser.Serialize(MainList);
return new JsonResult { Data = json, JsonRequestBehavior = JsonRequestBehavior.AllowGet };
}
else
return new JsonResult { Data = "", JsonRequestBehavior = JsonRequestBehavior.AllowGet };
}
From my FetchTableDataAsync function I want to return a List of arraylists and send them over to GetMultipleTblResult :-
public async Task<IEnumerable<ArrayList>> FetchTableDataAsync(AdminBundle abundleList)
{
AdminBundle abundle = new AdminBundle();
string innerMesage = string.Empty;
if (Session["AdminBundle"] != null)
abundle = (AdminBundle)Session["AdminBundle"];
ArrayList BulkList = null;
abundle.ListType = abundleList.ListType;
if (!string.IsNullOrEmpty(abundleList.ListType))
{
using (SMContext db = new SMContext())
{
switch (abundleList.ListType)
{
case "Category":
List<Category> CategoryList = null;
CategoryList = db.CatObj.Where(x => x.Status_Info == Constants.StatusInfoOne).ToList();
BulkList.Add(CategoryList);
break;
//Class Starts
case "Board":
List<Board> BoardList = null;
BoardList = db.BoardObj.Where(x => x.Status_Info == Constants.StatusInfoOne).ToList();
BulkList.Add(BoardList);
break;
default:
break;
//Main default Ends
}
}
}
return await BulkList; //Getting Error 'ArrayList' does not contain a definition for 'GetAwaiter' and no extension method 'GetAwaiter' accepting a first argument of type 'ArrayList' could be found (are you missing a using directive or an assembly reference?)
}
Basically I want to return set of multiple lists asynchronously from later function(FetchTableDataAsync) to previous function(GetMultipleTblResult) and then pass it to my angular.js file in JSON format.
EDIT:
So with help of #JohnWu I've done this bit:-
[HttpPost]
[LogInFilter]
public JsonResult GetMultipleTblResult(AdminBundle aBundleFetch)
{
if (!string.IsNullOrEmpty(aBundleFetch.ListType) && aBundleFetch.ListType.Equals(Constants.Board))
{
Task<AllTblListClass> AllTblObj = GetTableDataAsync(aBundleFetch);
//var jsonSerialiser = new JavaScriptSerializer();
//var json = jsonSerialiser.Serialize(AllTblObj);
return new JsonResult { Data = "", JsonRequestBehavior = JsonRequestBehavior.AllowGet };
}
else
return new JsonResult { Data = "", JsonRequestBehavior = JsonRequestBehavior.AllowGet };
}
public async Task<AllTblListClass> GetTableDataAsync(AdminBundle abundleList)
{
if (!string.IsNullOrEmpty(abundleList.ListType) && abundleList.ListType.Equals(Constants.Board))
{
return new AllTblListClass
{
BoardObj = await FetchBoardsAsync(),
ClsObj = await FetchClassAsync(),
ClsSubObj = await FetchClassSubAsync(),
MatTypeObj = await FetchMaterialTAsync(),
ClassSubMatRelationObj = await FetchClassSubMatRelAsync()
};
}
else
{
return new AllTblListClass { };
}
}
public async Task<List<ClassSubMatRelation>> FetchClassSubMatRelAsync()
{
using (SMContext db = new SMContext())
{
return await Task<List<ClassSubMatRelation>>.Run(() => db.ClassSubMatRelationObj.Where(x => x.Status_Info == Constants.StatusInfoOne).ToList()); // It executes untill here and then sleeps for endless time.
}
} //I'm not writing all functions as it will create a long question
But on this line of code:-
return await Task<List<ClassSubMatRelation>>.Run(() => db.ClassSubMatRelationObj.Where(x => x.Status_Info == Constants.StatusInfoOne).ToList());
The execution sleeps and nothing happens. There isn't any Error or Exception generating.
From the end of your second method:
return await BulkList;
Here, BulkList is declared as ArrayList. There is no need for this method to be async or involve Task<T> in any way, so the most appropriate option is simply to remove all the async and Task from that method. If you need to expose it as a Task<T> - Task.FromResult may be of use, but it is sub-optimal.
It seems you want a single function to return either a list of categories or a list of boards. If boards and categories are not related (e.g. they do not share a common interface), then this is a questionable design. How is the caller going to call it? The caller at some point has to know the difference, and the developer has to know in order to put the list into something type-specific so the objects can be read. If the caller knows the difference anyway, why not use two separate functions? For example
public async Task<IEnumerable<Category>> FetchCategoriesAsync(AdminBundle abundleList)
{
if (abundleList.ListType != "Category") throw new ArgumentException("abundleList");
AdminBundle abundle = Session["AdminBundle"] as AdminBundle;
abundle.ListType = abundleList.ListType;
using (SMContext db = new SMContext())
{
return await Task<List<Category>>.Run( () => db.CatObj.Where(x => x.Status_Info == Constants.StatusInfoOne).ToList());
}
}
Notice in this example the db call is wrapped in a task and awaited. This will give you the asynchronicity that you are looking for (a method can't act async unless there is an await inside it somewhere).
If you want to be able to get categories and boards at the same time, you can implement a wrapper function on top of it, like this:
class TableData
{
public List<Catgeory> Categories { get; set; }
public List<Boards> Boards { get; set; }
}
public async Task<TableData> GetTableDataAsync(AdminBundle abundleList)
{
return new TableData
{
Categories = await FetchCategoriesAsync(abundleList),
Boards = await FetchBoardsAsync(abundleList);
};
}
I need to extract the route data (Controller, Action etc) from an arbitrary request path (not related to the current request) such as / or /account/manage. In previous versions of Asp.Net Mvc this could be accomplished like this:
var request = new HttpRequest(null, "http://localhost:3333/Home/About", "testvalue=1");
var response = new HttpResponse(new StringWriter());
var httpContext = new HttpContext(request, response);
var routeData = RouteTable.Routes.GetRouteData(new HttpContextWrapper(httpContext));
var values = routeData.Values;
// The following should be true for initial version of mvc app.
values["controller"] == "Home"
values["action"] == "Index"
Source
This solution is not optimal since it requires a fully qualified Url instead of just a request path.
You can use TemplateMatcher to extract route values:
public class RouteMatcher
{
public static RouteValueDictionary Match(string routeTemplate, string requestPath)
{
var template = TemplateParser.Parse(routeTemplate);
var matcher = new TemplateMatcher(template, GetDefaults(template));
var values = new RouteValueDictionary();
var moduleMatch = matcher.TryMatch(requestPath, values);
return values;
}
// This method extracts the default argument values from the template.
private static RouteValueDictionary GetDefaults(RouteTemplate parsedTemplate)
{
var result = new RouteValueDictionary();
foreach (var parameter in parsedTemplate.Parameters)
{
if (parameter.DefaultValue != null)
{
result.Add(parameter.Name, parameter.DefaultValue);
}
}
return result;
}
}
And example usage:
var template = "{controller=Home}/{action=Index}/{id?}";
var routeValues = RouteMatcher.Match(template, "<your path>");
See this article: https://blog.markvincze.com/matching-route-templates-manually-in-asp-net-core/
Solved!!! - See last edit.
In my MVC app I make calls out to a Web API service with HMAC Authentication Filterign. My Get (GetMultipleItemsRequest) works, but my Post does not. If I turn off HMAC authentication filtering all of them work. I'm not sure why the POSTS do not work, but the GETs do.
I make the GET call from my code like this (this one works):
var productsClient = new RestClient<Role>(System.Configuration.ConfigurationManager.AppSettings["WebApiUrl"],
"xxxxxxxxxxxxxxx", true);
var getManyResult = productsClient.GetMultipleItemsRequest("api/Role").Result;
I make the POST call from my code like this (this one only works when I turn off HMAC):
private RestClient<Profile> profileClient = new RestClient<Profile>(System.Configuration.ConfigurationManager.AppSettings["WebApiUrl"],
"xxxxxxxxxxxxxxx", true);
[HttpPost]
public ActionResult ProfileImport(IEnumerable<HttpPostedFileBase> files)
{
//...
var postResult = profileClient.PostRequest("api/Profile", newProfile).Result;
}
My RestClient builds like this:
public class RestClient<T> where T : class
{
//...
private void SetupClient(HttpClient client, string methodName, string apiUrl, T content = null)
{
const string secretTokenName = "SecretToken";
client.BaseAddress = new Uri(_baseAddress);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
if (_hmacSecret)
{
client.DefaultRequestHeaders.Date = DateTime.UtcNow;
var datePart = client.DefaultRequestHeaders.Date.Value.UtcDateTime.ToString(CultureInfo.InvariantCulture);
var fullUri = _baseAddress + apiUrl;
var contentMD5 = "";
if (content != null)
{
var json = new JavaScriptSerializer().Serialize(content);
contentMD5 = Hashing.GetHashMD5OfString(json); // <--- Javascript serialized version is hashed
}
var messageRepresentation =
methodName + "\n" +
contentMD5 + "\n" +
datePart + "\n" +
fullUri;
var sharedSecretValue = ConfigurationManager.AppSettings[_sharedSecretName];
var hmac = Hashing.GetHashHMACSHA256OfString(messageRepresentation, sharedSecretValue);
client.DefaultRequestHeaders.Add(secretTokenName, hmac);
}
else if (!string.IsNullOrWhiteSpace(_sharedSecretName))
{
var sharedSecretValue = ConfigurationManager.AppSettings[_sharedSecretName];
client.DefaultRequestHeaders.Add(secretTokenName, sharedSecretValue);
}
}
public async Task<T[]> GetMultipleItemsRequest(string apiUrl)
{
T[] result = null;
try
{
using (var client = new HttpClient())
{
SetupClient(client, "GET", apiUrl);
var response = await client.GetAsync(apiUrl).ConfigureAwait(false);
response.EnsureSuccessStatusCode();
await response.Content.ReadAsStringAsync().ContinueWith((Task<string> x) =>
{
if (x.IsFaulted)
throw x.Exception;
result = JsonConvert.DeserializeObject<T[]>(x.Result);
});
}
}
catch (HttpRequestException exception)
{
if (exception.Message.Contains("401 (Unauthorized)"))
{
}
else if (exception.Message.Contains("403 (Forbidden)"))
{
}
}
catch (Exception)
{
}
return result;
}
public async Task<T> PostRequest(string apiUrl, T postObject)
{
T result = null;
try
{
using (var client = new HttpClient())
{
SetupClient(client, "POST", apiUrl, postObject);
var response = await client.PostAsync(apiUrl, postObject, new JsonMediaTypeFormatter()).ConfigureAwait(false); //<--- not javascript formatted
response.EnsureSuccessStatusCode();
await response.Content.ReadAsStringAsync().ContinueWith((Task<string> x) =>
{
if (x.IsFaulted)
throw x.Exception;
result = JsonConvert.DeserializeObject<T>(x.Result);
});
}
}
catch (HttpRequestException exception)
{
if (exception.Message.Contains("401 (Unauthorized)"))
{
}
else if (exception.Message.Contains("403 (Forbidden)"))
{
}
}
catch (Exception)
{
}
return result;
}
//...
}
My Web API Controller is defined like this:
[SecretAuthenticationFilter(SharedSecretName = "xxxxxxxxxxxxxxx", HmacSecret = true)]
public class ProfileController : ApiController
{
[HttpPost]
[ResponseType(typeof(Profile))]
public IHttpActionResult PostProfile(Profile Profile)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
GuidValue = Guid.NewGuid();
Resource res = new Resource();
res.ResourceId = GuidValue;
var data23 = Resourceservices.Insert(res);
Profile.ProfileId = data23.ResourceId;
_profileservices.Insert(Profile);
return CreatedAtRoute("DefaultApi", new { id = Profile.ProfileId }, Profile);
}
}
Here is some of what SecretAuthenticationFilter does:
//now try to read the content as string
string content = actionContext.Request.Content.ReadAsStringAsync().Result;
var contentMD5 = content == "" ? "" : Hashing.GetHashMD5OfString(content); //<-- Hashing the non-JavaScriptSerialized
var datePart = "";
var requestDate = DateTime.Now.AddDays(-2);
if (actionContext.Request.Headers.Date != null)
{
requestDate = actionContext.Request.Headers.Date.Value.UtcDateTime;
datePart = requestDate.ToString(CultureInfo.InvariantCulture);
}
var methodName = actionContext.Request.Method.Method;
var fullUri = actionContext.Request.RequestUri.ToString();
var messageRepresentation =
methodName + "\n" +
contentMD5 + "\n" +
datePart + "\n" +
fullUri;
var expectedValue = Hashing.GetHashHMACSHA256OfString(messageRepresentation, sharedSecretValue);
// Are the hmacs the same, and have we received it within +/- 5 mins (sending and
// receiving servers may not have exactly the same time)
if (messageSecretValue == expectedValue
&& requestDate > DateTime.UtcNow.AddMinutes(-5)
&& requestDate < DateTime.UtcNow.AddMinutes(5))
goodRequest = true;
Any idea why HMAC doesn't work for the POST?
EDIT:
When SecretAuthenticationFilter tries to compare the HMAC sent, with what it thinks the HMAC should be they don't match. The reason is the MD5Hash of the content doesn't match the MD5Hash of the received content. The RestClient hashes the content using a JavaScriptSerializer.Serialized version of the content, but then the PostRequest passes the object as JsonMediaTypeFormatted.
These two types don't get formatted the same. For instance, the JavaScriptSerializer give's us dates like this:
\"EnteredDate\":\"\/Date(1434642998639)\/\"
The passed content has dates like this:
\"EnteredDate\":\"2015-06-18T11:56:38.6390407-04:00\"
I guess I need the hash to use the same data that's passed, so the Filter on the other end can confirm it correctly. Thoughts?
EDIT:
Found the answer, I needed to change the SetupClient code from using this line:
var json = new JavaScriptSerializer().Serialize(content);
contentMD5 = Hashing.GetHashMD5OfString(json);
To using this:
var json = JsonConvert.SerializeObject(content);
contentMD5 = Hashing.GetHashMD5OfString(json);
Now the sent content (formatted via JSON) will match the hashed content.
I was not the person who wrote this code originally. :)
Found the answer, I needed to change the SetupClient code from using this line:
var json = new JavaScriptSerializer().Serialize(content);
contentMD5 = Hashing.GetHashMD5OfString(json);
To using this:
var json = JsonConvert.SerializeObject(content);
contentMD5 = Hashing.GetHashMD5OfString(json);
Now the content used for the hash will be formatted as JSON and will match the sent content (which is also formatted via JSON).