[ProducesResponseType(200, Type = typeof(OperationResponse<Plan>))]
[ProducesResponseType(400, Type = typeof(OperationResponse<Plan>))]
[HttpGet("{id}")]
public async Task<IActionResult> Get([FromRoute] string id)
{
string userId = User.FindFirst(ClaimTypes.NameIdentifier).Value;
System.Diagnostics.StackTrace t = new System.Diagnostics.StackTrace();
Console.WriteLine("stack is " + t);
var plan = await _plansService.GetPlanById(id, userId);
if (plan == null)
return BadRequest(new OperationResponse<string>
{
IsSuccess = false,
Message = "Invalid operation",
});
return Ok(new OperationResponse<Plan>
{
Record = plan,
Message = "Plan retrieved successfully!",
IsSuccess = true,
OperationDate = DateTime.UtcNow
});
}
string id should be of type guid and instead I'm getting the word 'search' instead of id which should be a guid for a particular product. How do I trace the value.... or the mapping?
Your URL /api/plans/search?query=you&page=1 has nothing that could be parsed as a GUID in it. Presumably the route prefix on the controller is /api/plans/ so in declaring id as FromRoute in your action you have a route of /api/plans/{id}. Hence you end up with "search" as your id when calling GET with the URL provided.
You could try the following:
[HttpGet("search")]
public async Task<IActionResult> Get([FromQuery] string id)
Then call it as follows:
GET /api/plans/search?id=<guid_string>&page=1`
id will then be set to a string that can be parsed as a guid.
I fixed it by changing api to
var response = await client.GetProtectedAsync<ProductsCollectionPagingResponse>($"{_baseUrl}/api/plans/query={query}/page={page}");
from
var response = await client.GetProtectedAsync<ProductsCollectionPagingResponse>($"{_baseUrl}/api/plans?query={query}&page={page}");
Related
I'm working on an ASP.NET Coe Web API project.
I have a post method (route) and I need it to call another get method:
[HttpGet(Name = "Profile")]
public async Task<ActionResult<ProfileResponseDto>> GetProfile(string username)
If I call :
return RedirectToRoute("Profile", new { username = profile.UserName });
I will end up with Get resource.
But what I want is to take the result of that Get method and continue in my post method.
In Python its something like
amount = requests.get("http://"+catalogIpAddress+":"+port+"/count/" + str(id))
HttpResponse.RedirectToRoute Method Redirects a request to a new URL by using route parameter values, a route name, or both this means that you cannot continue with the last URL
HttpResponse.RedirectToRoute Method
but you can return value from another action result like
var result = await controller.GetTodosAsync();
so you can use like this :
[HttpGet(Name = "Profile")]
public async Task<string> GetProfile(string username)
{
return await Task.Run(()=> "hello") ;
}
[HttpPost(Name = "Profile")]
public async Task<string> PostProfile(string username)
{
string result = await this.GetProfile(username);
result = result + " from post";
return await Task.Run(() => result);
}
I just started to migrate my project to .NET Core 6, using ASP.NET Boilerplate with Kendo controls.
The application will do some operations (insert/update) and throw some custom error messages to the client if any database operation failed.
The application sends the raw error message instead of the custom formatted JSON. Am I missing something to configure?
Is there any way to get the custom error message which I have returned from the controller?
Controller:
[ValidateAntiForgeryToken()]
[DontWrapResult]
[DisableValidation]
[HttpPost]
public ActionResult CreateOrUpdate([DataSourceRequest] DataSourceRequest request,
[Bind(Prefix = "models")] IEnumerable<InstallationTeamMemberDto> teamMembers,
long id, string teamName, string description, string category, bool isActive,
long? teamLeadId, string contactNumber, string remarks)
{
List<InstallationTeamMemberDto> teamMembersList = teamMembers.ToList();
try
{
if (ValidateMe(teamMembersList, id, teamName, description, category, isActive, teamLeadId, contactNumber, remarks) && ModelState.IsValid)
{
id = _installationTeamAppService.CreateOrUpdateAsync(teamMembersList, id, teamName, description,
category, isActive, teamLeadId, contactNumber, remarks);
}
}
catch (Exception ex)
{
ModelState.AddModelError("", "Unable to Save the data! Internal error occured." + ex.Message.ToString());
var res1 = teamMembers.ToDataSourceResult(request, ModelState);
return Json(new
{
Data = res1.Data,
Errors = res1.Errors,
Id = id,
IsSuccess = 0
}, JsonSetting.DefaultJsonSerializerSetting);
}
var res = teamMembers.ToDataSourceResult(request, ModelState);
// Returning the Id to save the Technical Details information
return Json(new
{
Data = res.Data,
Errors = res.Errors,
Id = id,
IsSuccess = 1
}, JsonSetting.DefaultJsonSerializerSetting);
}
Result:
ASP.NET Boilerplate's Conventional Unit of Work is scoped around your Controller action.
You need to place the try/catch around the Unit of Work scope:
[UnitOfWork(IsDisabled = true)] // Add this
public ActionResult CreateOrUpdate(...)
{
List<InstallationTeamMemberDto> teamMembersList = teamMembers.ToList();
try
{
using (var uow = _unitOfWorkManager.Begin()) // Add this
{ //
if (ValidateMe(...)
{
id = _installationTeamAppService.CreateOrUpdate(...);
}
uow.Complete(); // Add this
} //
}
...
}
Instead of beginning a unit of work explicitly in your controller, you could add a [UnitOfWork] attribute on your application service method:
[UnitOfWork] // Add this
public void CreateOrUpdate(...)
You can require a new unit of work, but you will have the unnecessary ambient unit of work:
// [UnitOfWork(IsDisabled = true)] // -
public ActionResult CreateOrUpdate(...)
{
List<InstallationTeamMemberDto> teamMembersList = teamMembers.ToList();
try
{
// using (var uow = _unitOfWorkManager.Begin()) // -
using (var uow = _unitOfWorkManager.Begin(TransactionScopeOption.RequiresNew)) // +
{
if (ValidateMe(...)
{
id = _installationTeamAppService.CreateOrUpdate(...);
}
uow.Complete();
}
}
...
}
so for now it will match both of the actions by the route api/comments, I want the second one to be api/comments?blogId=1, but I don't want it to be api/comments/{blogId}.
//Get api/comments
[HttpGet]
[Route("/")]
public async Task<IActionResult> GetAll()
{
var comments = await _context.Comments.ToListAsync();
if(comments != null)
return Ok(new { status = 200, comments });
return NotFound();
}
//Get api/comments?blogId=1
[HttpGet]
public async Task<IActionResult> GetCommentsBy(int blogId)
{
var allComments = await _context.Comments.ToListAsync();
if (allComments != null)
{
var commentsByBlogId = allComments.Where(c => c.BlogId == blogId);
return Ok(new { status = 200, comments = commentsByBlogId });
}
return NotFound();
}
Routes are unique by looking at there themplate. Even if you're using blogId as query parameter, the two actions are using the same route template which is this api/comments.
To do what you're trying to do is to using only have one action that will return result when you send a blogId or not.
So just add one action Get and the logic should look like below:
[HttpGet]
public async Task<IActionResult> GetComments(int? blogId /* blogId i nullable so it is not required */)
{
// Gets the comments query but don't execute it yet. So no call to ToListAsync() here.
var commentsQuery = _context.Comments;
if (blogId.HasValue)
{
// When you've a blogId set in the query then add a filter to the query.
commentsQuery = commentsQuery.Where(c => c.BlogId == blogId);
}
var comments = await commentsQuery.ToListAsync();
// When the list is empty just send it as it is. the caller should be able to handle the case where the comments list is empty and status code is 200
// You also don't need to set the status code in your responde body. The caller should be able to get the response status first before checking the response body.
return Ok(comments);
}
In my IdentityConfig.cs
manager.PasswordValidator = new PasswordValidator
{
RequiredLength = 6,
RequireNonLetterOrDigit = true,
RequireDigit = true,
RequireLowercase = true,
RequireUppercase = true,
};
In my Web MVC UI, this is my action method
HttpResponseMessage responsePost = GlobalVariables.WebApiClient.PostAsJsonAsync("Account/Register", registerBindingModel).Result;
if (responsePost.IsSuccessStatusCode)
{
ViewBag.Message = "Added";
}
else
{
ViewBag.Message = "Internal server Error: " + responsePost.ReasonPhrase;
}
The code works. I can post data if it passes validation logic for passwords. But if it fails, it just throw back Internal server Error: Bad Request to my ui. But when I test it using Postman, I can see the error message from identity. Example Passwords must have at least one lowercase ('a'-'z').
Where this error message stored in HttpResponseMessage?
The API method for Register
[AllowAnonymous]
[HttpPost]
[Route("Register")]
public async Task<IHttpActionResult> Register(RegisterBindingModel model)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var user = new ApplicationUser() { UserName = model.Email, Email = model.Email };
IdentityResult result = await UserManager.CreateAsync(user, model.Password);
if (!result.Succeeded)
{
return GetErrorResult(result);
}
return Ok();
}
private IHttpActionResult GetErrorResult(IdentityResult result)
{
if (result == null)
{
return InternalServerError();
}
if (!result.Succeeded)
{
if (result.Errors != null)
{
foreach (string error in result.Errors)
{
ModelState.AddModelError("", error);
}
}
if (ModelState.IsValid)
{
// No ModelState errors are available to send, so just return an empty BadRequest.
return BadRequest();
}
return BadRequest(ModelState);
}
return null;
}
Based upon the comments, you need to parse the string content which returns a JSON string assuming your API is designed in that manner.
I am not sure about your API code but if you need to send your ModelState, you can send it in JSON form using:
return Json(ModelState);
You should not use IsSuccessStatusCode for a way to check for your logic. Basically it is a value that indicates if the HTTP response was successful. It is true if HttpStatusCode was in the Successful range (200-299); otherwise false. The algorithm for IsSuccessStatusCode is shown below:
public bool IsSuccessStatusCode
{
get { return ((int)statusCode >= 200) && ((int)statusCode <= 299); }
}
You can clearly see that the above function only depends on the statusCode received from the API.
Therefore you need to define your logic based on the content that you receive from the API. Create a model that will hold the response from the API based on the condition that you define in the API.
HttpResponseMessage responsePost = GlobalVariables.WebApiClient.PostAsJsonAsync("Account/Register", registerBindingModel).Result;
MyAPIResponse model=new MyAPIResponse();
string content=string.Empty;
if (responsePost.IsSuccessStatusCode)
{
content = response.Content.ReadAsStringAsync().Result;
model = JsonConvert.DeserializeObject<MyAPIResponse>(content);
//or if you do not want a model structure for the parsing
//var parsedmodel = JsonConvert.DeserializeObject<dynamic>(content);
}
ViewBag.Message=model.message;
I was not able to parse the content posted in your comment since it is not a valid JSON string. You would need to generate the correct JSON format for your logic.
EDIT:
So you can send the ModelState errors as JSON using a extension method:
public static class ModelStateHelper
{
public static IEnumerable Errors(this ModelStateDictionary modelState)
{
if (!modelState.IsValid)
{
return modelState.ToDictionary(kvp => kvp.Key,
kvp => kvp.Value.Errors
.Select(e => e.ErrorMessage).ToArray())
.Where(m => m.Value.Any());
}
return null;
}
}
And then call that extension method from the Controller action:
if (!ModelState.IsValid)
{
return Json(new { Errors = ModelState.Errors() }, JsonRequestBehavior.AllowGet);
}
RedirectToRoute throws InvalidOperationException: No route matches the supplied values, when I pass route-value from query-parameter (which is a route-template-string) but works fine when query-parameter is null.
I'm trying to redirect to returnUrl using RedirectToRoute(routeName, routeValues) after login but any attempt to do so throws InvalidOperationException: No route matches the supplied values, but works when returnUrl is null or empty.
Here I'm converting route-template to route-name using FormatRoute(route-template
[Route("/"), HttpGet("account-login/{returnUrl:alpha?}", Name = "account_login"), AllowAnonymous]
public async Task<IActionResult> Login([FromQuery] string returnUrl = null)
{
// Clear the existing external cookie to ensure a clean login process
await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);
return View(new LoginViewModel { ReturnUrl = returnUrl?.FormatRoute() });
}
Here I login and try to navigate to verify_login_token but it fails when returnUrl has a value but works when it is null or empty.
[AllowAnonymous, HttpPost("account-login/{returnUrl:alpha?}")]
public async Task<IActionResult> Login(LoginViewModel model, string returnUrl = null)
{
if (ModelState.IsValid)
{
ResponseHelper response = await context.LoginAsync(mapper.Map<LoginDTO>(model));
switch(response.Status)
{
case ResultCode.Success:
return this.RedirectToLocal(returnUrl ?? "/home_page");
case ResultCode.RequiresTwoFactor:
return this.RedirectToLocal("/verify_login_token", new { tokenProvider = ConstantHelper.TokenProvider, rememberMe = model.RememberMe, returnUrl }, ResponseHelper.ResponseHandler(response));
case ResultCode.UnconfirmedEmail:
AuthenticationDTO authentication = response.Data;
response = await context.GetUserClaimsAsync(model.EmailAddress);
if (response.Status.Equals(ResultCode.Success) && response.Data != null)
{
string callbackUrl = HtmlEncoder.Default.Encode(Url.Action("ConfirmEmail", "Account", new { userId = authentication.UserId, token = authentication.Token }, Request.Scheme));
string recipientName = response.GetData<IList<Claim>>().FirstOrDefault(r => r.Type.Equals("UserName")).Value;
response = await context.CreateConfirmationEmailAsync(model.EmailAddress, recipientName, callbackUrl);
}
break;
case ResultCode.Error:
case ResultCode.Exception:
return this.RedirectToLocal("/account_login", null, ResponseHelper.ResponseHandler(response));
}
}
I'm trying to navigate to this action method
[AllowAnonymous, HttpGet("verify-login-token/{tokenProvider:alpha}/{rememberMe:bool}/{returnUrl:alpha?}", Name = "verify_login_token")]
public async Task<IActionResult> VerifyLoginToken(string tokenProvider, bool rememberMe, string returnUrl = null)
{
// Require that the user has already logged in via username/password or external login
ResponseHelper response = await context.GetTwoFactorAuthenticationUserAsync();
if (!response.Status.Equals(ResultCode.Success))
{
this.SetViewErrorMessage(response);
return View("Error");
}
return View(new VerifyLoginTokenVM { TokenProvider = tokenProvider, RememberMe = rememberMe, ReturnUrl = returnUrl });
}
I have a helper function to help me validate routes
public static IActionResult RedirectToLocal(this Controller context, string routeName, object routeValues = null, ResponseFormat response = null)
{
if (!string.IsNullOrEmpty(routeName))
{
if (context.Url.IsLocalUrl(routeName))
{
routeName = routeName.Contains("/", System.StringComparison.CurrentCultureIgnoreCase) ? routeName.TrimStart('/') : routeName;
if (response != null)
{
return context.RedirectToRoute(routeName, routeValues).WithResponseData(response);
}
else
{
return context.RedirectToRoute(routeName, routeValues);
}
}
}
return context.RedirectToRoute("home_page");
}
Any attempt to redirect to the url in returnUrl fails: for example the following : this.RedirectToLocal(returnUrl ?? "/home_page"); does not work. I have added a helper function to convert any route-template to route-name using code below:
public static string FormatRoute(this string routeTemplate)
{
if(string.IsNullOrEmpty(routeTemplate))
{
return "/home_page";
}
return routeTemplate.Replace("-", "_");
}
If returnUrl has /create-user-profile, then FormatRoute("/create-user-profile") returns /create_user_profile
Currently I'm getting InvalidOperationException: No route matches the supplied values, when I try to redirect to returnUrl. I'm looking for help in understanding what I did wrong and how to correct that mistake.
RedirectToRoute uses a route name, while you seem to pass an url instead.
A Redirect, which redirects to an url, might be sufficient for your use case.
Edit: To include the routeValues in the url, with a little rewrite you could use QueryString, e.g.:
var uri = "/verify_login_token" + QueryString.Create(new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("userId", "yourUserId"),
new KeyValuePair<string, string>("token", "yourToken"),
}).ToUriComponent();