500 Error - Unable to select and perform a post action - winforms

I am not good with Web API. Here is my problem. I send an Json serialized object from my Windows Form Application. The object is an Entity table. When I do a get response it returns a 500 server error. Basically I plan to have multiple post methods in one controller which I may not be doing right. So I need you guys to guide me on what I have been doing wrong.
Here is my Controller:
[ResponseType(typeof(HttpWebResponse)), HttpPost, ActionName("MerchandiseApi")]
public HttpResponseMessage PostMain(IList<IMF_Main> mainFromConsolidator)
{
if (!ModelState.IsValid)
return Request.CreateResponse(HttpStatusCode.BadRequest, 2);
using (var anthill = new AnthillConsolidatorEntities())
{
var main = new IMF_Main();
foreach (var item in mainFromConsolidator)
{
main.BrandID = item.BrandID;
main.ItemID = item.ItemID;
main.CategoryID = item.CategoryID;
main.SubCategoryID = item.SubCategoryID;
main.ClassID = item.ClassID;
main.GenderID = item.GenderID;
main.CoaID = item.CoaID;
main.SubCoaID = item.SubCoaID;
main.First_SRP = item.First_SRP;
main.Current_SRP = item.Current_SRP;
main.Previous_SRP = item.Previous_SRP;
main.isSenior = item.isSenior;
main.isActive = item.isActive;
main.DateCreated = item.DateCreated;
anthill.IMF_Main.Add(main);
anthill.SaveChanges();
}
}
return Request.CreateResponse(HttpStatusCode.OK, 1);
}
Here's my WebApiConfig:
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.Routes.MapHttpRoute(
name: "MerchandiseApi",
routeTemplate: "api/{controller}/{action}"
);
}
Here is where the Uri gets built: I have 2 more tables to send but I will start with this. This goes to my first Post method to the server
var jsonMain = JsonConvert.SerializeObject(consolidatorEntities.IMF_Main, Formatting.None);
HttpPost("http://localhost:50826/api/Merchandise/PostMain", jsonMain) == 1.ToString()
public string HttpPost(string uri, string json)
{
string content = "";
try
{
var request = (HttpWebRequest)WebRequest.Create(uri);
request.Method = "POST";
request.Accept = "application/json";
request.ContentType = "application/json";
byte[] bodyBytes = Encoding.UTF8.GetBytes(json);
request.GetRequestStream().Write(bodyBytes, 0, bodyBytes.Length);
request.GetRequestStream().Close();
var response = (HttpWebResponse)request.GetResponse();
var sr = new StreamReader(response.GetResponseStream(), Encoding.GetEncod
ing("UTF-8"));
content = sr.ReadToEnd();
sr.Close();
}
catch (Exception ex)
{
MessageBox.Show("Error sending data to Anthill \nException: " + ex, "Monytron - Consolidator", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
return content;
}

Problem
The main problem is with your routing. Routes will check in order so when you post a request to http://localhost:50826/api/Merchandise/PostMain and you have these routes in order:
"api/{controller}/{id}"
"api/{controller}/{action}"
So the first route will match:
If your PostMain method is the only action with [HttpPost], then mainFromConsolidator will be null in your foreach loop you will receive a NullReferenceException that result in a 500 error.
If you have multiple method decorated with [HttpPost], then the call is ambiguous between those actions and you will receive an InvalidOperationExpception with "Multiple actions were found that match the request" message that result in a 500 error.
The other problem is you are using an ActionName("MerchandiseApi") but didn't post to that action.
Solution
You can use multiple solutions. As an option you can define only one route:
"api/{controller}/{action}/{id}"
This way you can create a controller that contains actions like these:
public class SomeController
{
// matches GET /api/some/action1
[HttpGet]
public HttpResponseMessage Action1()
// matches GET /api/some/action2/5
[HttpGet]
public HttpResponseMessage Action2(int id)
// matches POST /api/some/action3
[HttpPost]
public HttpResponseMessage Action3(SomeType someParameter)
// matches POST /api/some/action4
[HttpPost]
public HttpResponseMessage Action4(SomeType someParameter)
}
Anyway if you decide to define multiple routes, pay attention that routes will match in order and also if you used ActionName attribute, then use that name in url to call that action.

Related

Session Id (sid) is not assigned during automatic login via IdentityServer4, what gives?

Questions
First question, what determines if an sid claim is emitted from identityserver?
Second question, do I even need an sid? I currently have it included because it was in the sample..
Backstory
I have one website that uses IdentityServer4 for authentication and one website that doesn't. I've cobbled together a solution that allows a user to log into the non-identityserver4 site and click a link that uses one-time-access codes to automatically log into the identityserver4 site. Everything appears to work except the sid claim isn't passed along from identityserver to the site secured by identityserver when transiting from the non-identityserver site. If I log directly into the identityserver4 secured site the sid is included in the claims. Code is adapted from examples of automatically logging in after registration and/or impersonation work flows.
Here is the code:
One time code login process in identityserver4
public class CustomAuthorizeInteractionResponseGenerator : AuthorizeInteractionResponseGenerator
{
...
//https://stackoverflow.com/a/51466043/391994
public override async Task<InteractionResponse> ProcessInteractionAsync(ValidatedAuthorizeRequest request,
ConsentResponse consent = null)
{
string oneTimeAccessToken = request.GetAcrValues().FirstOrDefault(x => x.Split(':')[0] == "otac");
string clientId = request.ClientId;
//handle auto login handoff
if (!string.IsNullOrWhiteSpace(oneTimeAccessToken))
{
//https://benfoster.io/blog/identity-server-post-registration-sign-in/
oneTimeAccessToken = oneTimeAccessToken.Split(':')[1];
OneTimeCodeContract details = await GetOTACFromDatabase(oneTimeAccessToken);
if (details.IsValid)
{
UserFormContract user = await GetPersonUserFromDatabase(details.PersonId);
if (user != null)
{
string subjectId = await GetClientSubjectIdAsync(clientId, user.AdUsername);
var iduser = new IdentityServerUser(subjectId)
{
DisplayName = user.AdUsername,
AuthenticationTime = DateTime.Now,
IdentityProvider = "local",
};
request.Subject = iduser.CreatePrincipal();
//revoke token
bool? success = await InvalidateTokenInDatabase(oneTimeAccessToken);
if (success.HasValue && !success.Value)
{
Log.Debug($"Revoke failed for {oneTimeAccessToken} it should expire at {details.ExpirationDate}");
}
//https://stackoverflow.com/a/56237859/391994
//sign them in
await _httpContextAccessor.HttpContext.SignInAsync(IdentityServerConstants.DefaultCookieAuthenticationScheme, request.Subject, null);
return new InteractionResponse
{
IsLogin = false,
IsConsent = false,
};
}
}
}
return await base.ProcessInteractionAsync(request, consent);
}
}
Normal Login flow when logging directly into identityserver4 secured site (from sample)
public class AccountController : Controller
{
/// <summary>
/// Handle postback from username/password login
/// </summary>
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Login(LoginInputModel model)
{
Log.Information($"login request from: {Request.HttpContext.Connection.RemoteIpAddress.ToString()}");
if (ModelState.IsValid)
{
// validate username/password against in-memory store
if (await _userRepository.ValidateCredentialsAsync(model.Username, model.Password))
{
AuthenticationProperties props = null;
// only set explicit expiration here if persistent.
// otherwise we reply upon expiration configured in cookie middleware.
if (AccountOptions.AllowRememberLogin && model.RememberLogin)
{
props = new AuthenticationProperties
{
IsPersistent = true,
ExpiresUtc = DateTimeOffset.UtcNow.Add(AccountOptions.RememberMeLoginDuration)
};
};
var clientId = await _account.GetClientIdAsync(model.ReturnUrl);
// issue authentication cookie with subject ID and username
var user = await _userRepository.FindByUsernameAsync(model.Username, clientId);
var iduser = new IdentityServerUser(user.SubjectId)
{
DisplayName = user.UserName
};
await HttpContext.SignInAsync(iduser, props);
// make sure the returnUrl is still valid, and if yes - redirect back to authorize endpoint
if (_interaction.IsValidReturnUrl(model.ReturnUrl))
{
return Redirect(model.ReturnUrl);
}
return Redirect("~/");
}
ModelState.AddModelError("", AccountOptions.InvalidCredentialsErrorMessage);
}
// something went wrong, show form with error
var vm = await _account.BuildLoginViewModelAsync(model);
return View(vm);
}
}
AuthorizationCodeReceived in identityserver4 secured site
public partial class Startup
{
public void Configuration(IAppBuilder app)
{
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthorizationCodeReceived = async n =>
{
// use the code to get the access and refresh token
var tokenClient = new TokenClient(
tokenEndpoint,
electionClientId,
electionClientSecret);
var tokenResponse = await tokenClient.RequestAuthorizationCodeAsync(
n.Code, n.RedirectUri);
if (tokenResponse.IsError)
{
throw new Exception(tokenResponse.Error);
}
// use the access token to retrieve claims from userinfo
var userInfoClient = new UserInfoClient(
new Uri(userInfoEndpoint).ToString());
var userInfoResponse = await userInfoClient.GetAsync(tokenResponse.AccessToken);
Claim subject = userInfoResponse.Claims.Where(x => x.Type == "sub").FirstOrDefault();
// create new identity
var id = new ClaimsIdentity(n.AuthenticationTicket.Identity.AuthenticationType);
id.AddClaims(GetRoles(subject.Value, tokenClient, apiResourceScope, apiBasePath));
var transformedClaims = StartupHelper.TransformClaims(userInfoResponse.Claims);
id.AddClaims(transformedClaims);
id.AddClaim(new Claim("access_token", tokenResponse.AccessToken));
id.AddClaim(new Claim("expires_at", DateTime.Now.AddSeconds(tokenResponse.ExpiresIn).ToLocalTime().ToString()));
id.AddClaim(new Claim("refresh_token", tokenResponse.RefreshToken));
id.AddClaim(new Claim("id_token", n.ProtocolMessage.IdToken));
THIS FAILS -> id.AddClaim(new Claim("sid", n.AuthenticationTicket.Identity.FindFirst("sid").Value));
n.AuthenticationTicket = new AuthenticationTicket(
new ClaimsIdentity(id.Claims, n.AuthenticationTicket.Identity.AuthenticationType, "name", "role"),
n.AuthenticationTicket.Properties);
},
}
});
}
}
Questions again if you don't want to scroll back up
First question, what determines if an sid claim is emitted from identityserver?
Second question, do I even need an sid? I currently have it included because it was in the sample..

dotnet-core WebApp and multiple web api's access tokens using AzureB2C, MSAL

I have setup authentication/authorization for WebApp and Api and its working fine. The problem is when I have to introduce additional Api's which will be called from WebAPP.
The limitation is that you cannot ask a token with scopes mixing Web apis in one call. This is a limitation of the service (AAD), not of the library.
you have to ask a token for https://{tenant}.onmicrosoft.com/api1/read
and then you can acquire a token silently for https://{tenant}.onmicrosoft.com/api2/read as those are two different APIS.
I learned more about this from SO here and here
Since there is no full example other than couple of lines of code, I'm trying to find best way of implementing this solution.
Currently I have setup Authentication in Startup
services.AddAuthentication(sharedOptions =>
{
sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
sharedOptions.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
services.AddAzureAdB2C(options => Configuration.Bind("AzureAdB2C", options)).AddCookie();
AddAzureAdB2C is an customized extension method from Samples.
public static AuthenticationBuilder AddAzureAdB2C(this AuthenticationBuilder builder, Action<AzureAdB2COptions> configureOptions)
{
builder.Services.Configure(configureOptions);
builder.Services.AddSingleton<IConfigureOptions<OpenIdConnectOptions>, OpenIdConnectOptionsSetup>();
builder.AddOpenIdConnect();
return builder;
}
public class OpenIdConnectOptionsSetup : IConfigureNamedOptions<OpenIdConnectOptions>
{
public void Configure(OpenIdConnectOptions options)
{
options.ClientId = AzureAdB2COptions.ClientId;
options.Authority = AzureAdB2COptions.Authority;
options.UseTokenLifetime = true;
options.TokenValidationParameters = new TokenValidationParameters() { NameClaimType = "name" };
options.Events = new OpenIdConnectEvents()
{
OnRedirectToIdentityProvider = OnRedirectToIdentityProvider,
OnRemoteFailure = OnRemoteFailure,
OnAuthorizationCodeReceived = OnAuthorizationCodeReceived
};
}
public Task OnRedirectToIdentityProvider(RedirectContext context)
{
var defaultPolicy = AzureAdB2COptions.DefaultPolicy;
if (context.Properties.Items.TryGetValue(AzureAdB2COptions.PolicyAuthenticationProperty, out var policy) &&
!policy.Equals(defaultPolicy))
{
context.ProtocolMessage.Scope = OpenIdConnectScope.OpenIdProfile;
context.ProtocolMessage.ResponseType = OpenIdConnectResponseType.IdToken;
context.ProtocolMessage.IssuerAddress = context.ProtocolMessage.IssuerAddress.ToLower().Replace(defaultPolicy.ToLower(), policy.ToLower());
context.Properties.Items.Remove(AzureAdB2COptions.PolicyAuthenticationProperty);
}
else if (!string.IsNullOrEmpty(AzureAdB2COptions.ApiUrl))
{
context.ProtocolMessage.Scope += $" offline_access {AzureAdB2COptions.ApiScopes}";
context.ProtocolMessage.ResponseType = OpenIdConnectResponseType.CodeIdToken;
}
return Task.FromResult(0);
}
}
I guess the scope has to be set on this line for each API but this is part of pipeline.(in else if part of OnRedirectToIdentityProvide method above)
context.ProtocolMessage.Scope += $" offline_access {AzureAdB2COptions.ApiScopes}";
Following are api client configuration
services.AddHttpClient<IApiClient1, ApiClient1>()
.AddHttpMessageHandler<API1AccessTokenHandler>();
services.AddHttpClient<IApiClient2, ApiClient2>()
.AddHttpMessageHandler<API2AccessTokenHandler>();
Following is the code for acquiring token silently for API1.
public class API1AccessTokenHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
IConfidentialClientApplication publicClientApplication = null;
try
{
// Retrieve the token with the specified scopes
scopes = AzureAdB2COptions.ApiScopes.Split(' ');
string signedInUserID = _httpContextAccessor.HttpContext.User.FindFirst(ClaimTypes.NameIdentifier).Value;
publicClientApplication = ConfidentialClientApplicationBuilder.Create(AzureAdB2COptions.ClientId)
.WithRedirectUri(AzureAdB2COptions.RedirectUri)
.WithClientSecret(AzureAdB2COptions.ClientSecret)
.WithB2CAuthority(AzureAdB2COptions.Authority)
.Build();
new MSALStaticCache(signedInUserID, _httpContextAccessor.HttpContext).EnablePersistence(publicClientApplication.UserTokenCache);
var accounts = await publicClientApplication.GetAccountsAsync();
result = await publicClientApplication.AcquireTokenSilent(scopes, accounts.FirstOrDefault())
.ExecuteAsync();
}
catch (MsalUiRequiredException ex)
{
}
if (result.AccessToken== null)
{
throw new Exception();
}
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
return await base.SendAsync(request, cancellationToken);
}
}
Following is the code for acquiring token silently for API2, API2AccessTokenHandler.
public class API2AccessTokenHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
IConfidentialClientApplication publicClientApplication = null;
try
{
// Retrieve the token with the specified scopes
scopes = Constants.Api2Scopes.Split(' ');
string signedInUserID = _httpContextAccessor.HttpContext.User.FindFirst(ClaimTypes.NameIdentifier).Value;
publicClientApplication = ConfidentialClientApplicationBuilder.Create(AzureAdB2COptions.ClientId)
.WithRedirectUri(AzureAdB2COptions.RedirectUri)
.WithClientSecret(AzureAdB2COptions.ClientSecret)
.WithB2CAuthority(AzureAdB2COptions.Authority)
.Build();
new MSALStaticCache(signedInUserID, _httpContextAccessor.HttpContext).EnablePersistence(publicClientApplication.UserTokenCache);
var accounts = await publicClientApplication.GetAccountsAsync();
result = await publicClientApplication.AcquireTokenSilent(scopes, accounts.FirstOrDefault())
.ExecuteAsync();
}
catch (MsalUiRequiredException ex)
{
}
if (result.AccessToken== null)
{
throw new Exception();
}
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
return await base.SendAsync(request, cancellationToken);
}
}
Passing the scope while acquiring the token did not help. The token
is always null.
The account always have scope for Api1 but not for
Api2.
The scope of APi1 is added from the AzureB2COptions.ApiScope
as part of the ServiceCollection pipeline code in Startup.cs
I guess having separate calls to Acquire token is not helping in case of Api2 because scope is being set for Api1 in Startup.cs.
Please provide your valuable suggestions along with code samples.
UPDATE:
I'm looking something similar to WithExtraScopeToConsent which is designed for IPublicClientApplication.AcquireTokenInteractive. I need similar extension for ConfidentialClientApplicationBuilder to be used for AcquireTokenByAuthorizationCode
cca.AcquireTokenByAuthorizationCode(AzureAdB2COptions.ApiScopes.Split(' '), code)
.WithExtraScopeToConsent(additionalScopeForAPi2)
.ExecuteAsync();
Yes, we can have multiple scopes for same api not multiple scopes from different Apis.
In this sample, we retrieve the token with the specified scopes.
// Retrieve the token with the specified scopes
var scope = new string[] { api1_scope };
IConfidentialClientApplication cca = MsalAppBuilder.BuildConfidentialClientApplication();
var accounts = await cca.GetAccountsAsync();
AuthenticationResult result = await cca.AcquireTokenSilent(scope, accounts.FirstOrDefault()).ExecuteAsync();
var accessToken=result.AccessToken;
You can get the accessToken with different api scope.
// Retrieve the token with the specified scopes
var scope = new string[] { api2_scope };
IConfidentialClientApplication cca = MsalAppBuilder.BuildConfidentialClientApplication();
var accounts = await cca.GetAccountsAsync();
AuthenticationResult result = await cca.AcquireTokenSilent(scope, accounts.FirstOrDefault()).ExecuteAsync();
var accessToken=result.AccessToken;

NancyFX on Krestrel - Response Stream Closed

I'm attempting it make a simple proxy server that will try to stream back data from an IP camera (the IP camera doesn't honor OPTIONS and has some other issues!). I tried doing this using NancyFX and Krestrel with the following proxy module. The idea was to just get 1028 bytes of data in and write it to the output stream asynchronously until canceled.
Here is a sample Nancy Module:
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading;
using System.Threading.Tasks;
using Nancy;
namespace Server.Modules
{
public class Proxy : NancyModule
{
public Proxy() : base("api/proxy")
{
Get("/", ProxyPage);
}
private async Task<Response> ProxyPage(dynamic args, CancellationToken cancellationToken)
{
// Create HttpClient
using (var httpClient = new HttpClient()) // Make this global/cached and indexed by auth code
{
// Handle Authentication
var auth = string.Empty;
if (!string.IsNullOrEmpty(Request.Headers.Authorization) && Request.Headers.Authorization.Contains(" "))
auth = Request.Headers.Authorization.Split(' ')[1];
else if (!string.IsNullOrEmpty(Request.Query.authorization))
auth = Request.Query.authorization;
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", auth);
// Create Proxy REsponse object
var proxyResponse = new Response();
// Get Async
HttpResponseMessage response = await httpClient.GetAsync(Request.Query["url"],
HttpCompletionOption.ResponseHeadersRead, cancellationToken);
// Set Content Type
proxyResponse.ContentType = response.Content.Headers.ContentType.ToString();
// Set Status Code
proxyResponse.StatusCode = (HttpStatusCode)(int)response.StatusCode;
// Handle stream writing
proxyResponse.Contents = async s =>
{
var result = response.Content.ReadAsStreamAsync();
var data = new byte[1028];
int bytesRead;
while (!cancellationToken.IsCancellationRequested && (bytesRead = await result.Result.ReadAsync(data, 0, data.Length, cancellationToken)) > 0)
{
await s.WriteAsync(data, 0, bytesRead, cancellationToken);
await s.FlushAsync(cancellationToken);
}
response.Dispose();
};
// Return Response container
return proxyResponse;
}
}
}
}
When I run it, I get through the while loop a couple times but then get an exception in FrameResponseStream (Krestrel): "System.ObjectDisposedException: 'Cannot access a disposed object.'" It appears that the stream is being closed (_state = FrameStreamState.Closed -- https://github.com/aspnet/KestrelHttpServer/blob/rel/2.0.0/src/Microsoft.AspNetCore.Server.Kestrel.Core/Internal/Http/FrameResponseStream.cs) prematurely but I cannot figure out why or what I need to change to resolve it!
You should use ResponseContentRead instead of ResponseHeadersRead
HttpResponseMessage response = await httpClient.GetAsync(Request.Query["url"],
HttpCompletionOption.ResponseContentRead, cancellationToken);

Asp net core MVC Authorization with Active Directory

In an Asp net core MVC application, I use Active Directory for automatic login like this :
this.user = UserPrincipal.FindByIdentity(this.context, Environment.UserName);
and I get groups of the user with this :
public List<String> GetUserGroups()
{
List<String> groups = new List<String>();
foreach(GroupPrincipal gr in user.GetGroups())
{
groups.Add(gr.Name);
}
return groups;
}
And I would like to implement Autorisation with this groups, something like that :
[Authorize(Roles ="Admin")]
public IActionResult OnlyAdmin(){}
with something that link AD groups with authorization Roles or directly check authorization with AD groups if possible but I don't know how to do something like that.
note : I haven't any login/logout pages, it's only automatic.
EDIT
Don't know exactly why or how but it finaly work whithout any code and only with the user login in the PC not the user specified in this.user but it's fine like that.
But now I get a 404 error when I'm trying to access a denied page, why it's not a 401 or 403 error ? How can I redirect a denied access to a custom error page ?
You need to add the group in the ClaimsPrincipal class, i.e.
var claims = new List<Claim>();
claims.Add(new Claim(ClaimTypes.Name, username));
foreach (string userGroup in authResponse)
{
claims.Add(new Claim(ClaimTypes.Role, userGroup, ClaimValueTypes.String,"system","system"));
}
var principal = new ClaimsPrincipal(new ClaimsIdentity(claims, "authenticationScheme"));
Now use authorize attribute, either on controller or action as :
[Authorize(Roles = "guest,home")]
You can write an ErrorHandlingMiddleware as follows. You will need to register it in the startup file
app.UseMiddleware(typeof(ErrorHandlingMiddleware));
following is an example for the same.
public class ErrorHandlingMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger _logger;
public ErrorHandlingMiddleware(RequestDelegate next, ILogger<ErrorHandlingMiddleware> createLogger)
{
this._next = next;
this._logger = createLogger;
}
public async Task Invoke(HttpContext context)
{
var statusCode = HttpStatusCode.OK;
try
{
await _next.Invoke(context);
}
catch (Exception ex)
{
this._logger.LogError(ex, ex.Message);
switch (context.Response.StatusCode)
{
case (int)HttpStatusCode.NotFound:
statusCode = HttpStatusCode.NotFound;
break;
case (int)HttpStatusCode.Forbidden:
statusCode = HttpStatusCode.Forbidden;
break;
case (int)HttpStatusCode.BadRequest:
statusCode = HttpStatusCode.BadRequest;
break;
default:
statusCode = HttpStatusCode.InternalServerError;
break;
}
context.Response.StatusCode = (int)statusCode;
}
if (!context.Response.HasStarted)
{
context.Response.ContentType = "application/json";
var response = new { code = statusCode };
var json = JsonConvert.SerializeObject(response);
await context.Response.WriteAsync(json);
}
}
}

Unsupported Media Type Spring ReST resource

I have a simple method defined in my Rest Resource as below:
#RequestMapping(value = "/{studyId}/cases/{caseId}/exportlocation/{exportLocation}", method = RequestMethod.PUT)
#Timed
public void exportCase(#PathVariable Long studyId, #PathVariable Long caseId, #PathVariable String exportLocation,
#RequestBody Case acase) throws Exception {
log.debug("REST request to export Case {} for Study : {}", acase, studyId);
String exportFileName = exportService.exportCase(acase, "test");
// if (exportFileName == null) {
// response.sendError(HttpServletResponse.SC_NOT_FOUND, "Can't Export");
// }
// return exportFileName;
}
When I make a call on the page, I can see the URL as being /app/rest/studies/1/cases/1/exportlocation/test
I have the Request Mapping defined as
#RequestMapping(value = StudyResource.REQUEST_MAPPING, produces = MediaType.APPLICATION_JSON_VALUE)
#Secured(AuthoritiesConstants.USER)
public class StudyResource {
private final Logger log = LoggerFactory.getLogger(StudyResource.class);
public static final String REQUEST_MAPPING = "/app/rest/studies";
But keep getting a 415 Unsupported Media type. Can someone please look at the lines of code and tell me what is wrong. I highly appreciate your time and help.
My JS layer from where the calls are made on the page are as shown"
$scope.exportCase = function(studyId, caseId, exportLocation){
StudyService.updatecase.get({studyId:studyId,caseId:caseId}).$promise.then(function(acase){
$scope.acase = acase;
console.log(acase);
});
StudyService.exportcase.exportc({studyId: studyId,caseId:caseId,exportLocation:exportLocation},$scope.acase,
function () {
AND JS Service part below
exportcase : $resource('app/rest/studies/:studyId/cases/:caseId/exportlocation/:exportLocation', {}, {
'exportc' : {
method : 'PUT',
params : {
studyId : '#studyId',
caseId : '#caseId',
exportLocation : '#exportLocation'
}
},
})

Resources