I have a GET endpoint in Asp.Net Core 2.2 where I want to return all records by default and if any query provided then I am returning a search query response. Here my query Dto never comes null even no query param provided. I want to keep Dto null when no query parameter provided.
My request Dto-
public sealed class SearchQueryDto
{
public SearchQueryDto()
{
Limit = 10;
Offset = 0;
}
public string Query { get; set; }
public string PhoneNumber { get; set; }
public string ClassificationId { get; set; }
public int Offset { get; set; }
public int Limit { get; set; }
}
My Endpoint-
[HttpGet]
public async Task<IActionResult> GetAllRecords([FromQuery] [CanBeNull] SearchQueryDto request)
{
if (request != null) // always not null
{
return Ok(await _service.Search(request));
}
return Ok(await _service.GetAll());
}
Here I am expecting request to null when no query parameter provided but it always appears initialized.
The docs lie:
Route data and query string values are used only for simple types.
This is simply not true. See also Bind query parameters to a model in ASP.NET Core, it just works.
This part of the docs is true:
When model binding occurs, the class is instantiated using the public default constructor.
So, the model class will always be instantiated. Unless it's bound from the body and the body is empty, corrupt or of the wrong content-type, but that's another story.
In your case, simply check whether the model doesn't have any properties set:
public sealed class SearchQueryDto
{
private const int DefaultLimit = 10;
private const int DefaultOffset = 0;
public string Query { get; set; }
public string PhoneNumber { get; set; }
public string ClassificationId { get; set; }
public int Offset { get; set; } = DefaultOffset;
public int Limit { get; set; } = DefaultLimit;
public bool IsDefault
{
get
{
return string.IsNullOrEmpty(Query)
&& string.IsNullOrEmpty(PhoneNumber)
&& string.IsNullOrEmpty(ClassificationId)
&& Offset == DefaultOffset
&& Limit == DefaultLimit;
}
}
}
And then in your controller:
if (!request.IsDefault)
{
// ...
}
you can set default null
[HttpGet]
public async Task<IActionResult> GetAllRecords([FromQuery] SearchQueryDto request = null)
Although you do not pass query string,the model would bind the default value,so you could not get null value.
In such case,you could fix it like below:
[HttpGet]
public async Task<IActionResult> GetAllRecords([FromQuery]SearchQueryDto request)
{
var query = Request.QueryString.HasValue;
if (query)
{
return Ok(await _service.Search(request));
}
return Ok(await _service.GetAll());
}
Related
My WebApi has a table for applications with the following class:
namespace Models.Public
{
[Index(nameof(UUID), nameof(UID), IsUnique = true)]
public class Application
{
public Application()
{
this.UUID = new Guid();
}
public int ID { get; set; }
public Guid UUID { get; set; }
[Required]
public string UID { get; set; }
public string Publisher { get; set; }
public string Name { get; set; }
public string Version { get; set; }
}
}
The field UUID and ID are unique, so I was able to generate the required HttpGet command to obtain the results matching for that.
However, I am trying to obtain an IEnumerable object of all the items that match the Publisher field. That is, return all object that have "Google" as their Publisher.
My attempts have not been successful and I am hoping for some advise to fix my code:
// GET: api/Application/<publisher>
[HttpGet("{publisher}")]
public async Task<ActionResult<IEnumerable<Application>>> GetApplication(string publisher)
{
var application = await _context.Application.ToListAsync(publisher);
if (application == null)
{
return NotFound();
}
return await _context.Application.ToListAsync();
}
Publisher is not a unique value, so I'd like to be able to return all items as a JSON object that have whatever Publisher I type in the list. If no matches, error handle with NotFound();.
You will need to filter using .Where, .Contains
// GET: api/Application/<publisher>
[HttpGet("{publisher}")]
public async Task<ActionResult<IEnumerable<ApplicationData>>> GetApplication(string publisher)
{
var applications = _context.Application.Where(a=>a.Publisher.Contains(publisher)));
/* You could also use == for exact match
var applications = _context.Application.Where(a=>a.Publisher == publisher));
*/
if (applications.Count() == 0)
{
return NotFound();
}
return await applications.ToListAsync();
}
I got an error while getting json data from POST method, am I doing something wrong
C# Code:
public IActionResult signupapi(UserSignUp user)
{
var model = new Models.SignUpModelAPI(HttpContext);
if (user == null)
{
return Content(model.ResponseJsonText(false, string.Format(model.Language("empty"),
HttpContext.Request.Method, HttpContext.Request.Path.Value), Class.CodeResponse.ERROR), new Microsoft.Net.Http.Headers.MediaTypeHeaderValue("application/json"));
}
if (!model.isAllowMethod("POST"))
{
return Content(model.ResponseJsonText(false,string.Format(model.Language("notallowmethod"),
HttpContext.Request.Method,HttpContext.Request.Path.Value),Class.CodeResponse.ERROR),new Microsoft.Net.Http.Headers.MediaTypeHeaderValue("application/json"));
}
return Content(JsonConvert.SerializeObject(user));
}
public class UserSignUp
{
public string fullname { get; set; }
public string username { get; set; }
public string email { get; set; }
public string password { get; set; }
}
And this is the result when i try on reqbin every value i get is null
You need to add FromBody attribute to get your data for the POST operation:
public IActionResult signupapi([FromBody]UserSignUp user)
You can read more on parameter binding on MSDN docs.
I am using this class (representing goal) as model:
public class Goal
{
public int Id { get; set; }
public int UserId { get; set; }
public String Name { get; set; }
public List<GoalDay> Days { get; set; } = new List<GoalDay>();
public bool IsAllDayGenerated { get; set; }
public DateTime StartDateGenerated { get; set; }
public TimeSpan LengthTimeGenerated { get; set; }
public int TotalValue { get; set; }
public string Unit { get; set; }
}
Method in service sending data:
public async Task AddGoal(Goal goal)
{
var result = await _http.PostAsJsonAsync<Goal>("api/goal", goal);
if (result.StatusCode != System.Net.HttpStatusCode.OK)
{
_toastService.ShowError(await result.Content.ReadAsStringAsync());
}
else
{
_toastService.ShowSuccess(await result.Content.ReadAsStringAsync());
}
Method in controller receiving data:
[HttpPost]
public async Task<IActionResult> CreateGoal(Goal goal)
{
var user = await _utilityService.GetUser();
goal.UserId = user.Id;
_context.Goals.Add(goal);
await _context.SaveChangesAsync();
return Ok("Goal added days:" + goal.Days.Count);
}
When I send data using HttpPost to server value saved in TimeSpan (in variable LengthTimeGenerated ) is there. In the upper part of picture can be seen printscreen of network traffic.
But afterward in controller data are gone. In the lower part of the picture can be seen printscreen of model zero value of the same variable in controller.
Looks like the classic case of the camel case changes in the System.Text.Json compared to Newtownsoft. (System.Text.Json is case sensitive by default)
Try adding serialization option to use camel case.
private readonly JsonSerializerOptions _jsonSerializerOptions = new() { PropertyNameCaseInsensitive = true };
// usage
public async Task AddGoal(Goal goal)
{
var result = await _http.PostAsJsonAsync<Goal>("api/goal", goal, _jsonSerializerOptions);
//... etc
}
Other example of usage:
var myParsedObject = await JsonSerializer.DeserializeAsync<SomeResponseType>(await response.Content.ReadAsStreamAsync(), _jsonSerializerOptions);
I have a web api built in .NET, within an endpoint i would like to redirect to an url which matches the the code inserted in database. the endpoint takes as entry the code and i am supposed to redirect to the corresponding url. For that i use the Redirect method which actually is not working. i did Console.Write to print if the url is null or empty but it exists. here is the code of my controller :
[HttpGet("{hash}")]
// [ProducesResponseType(302)]
//[ProducesResponseType(404)]
public async Task<IActionResult> redirectUrl(string hash)
{
var t = await new ServiceUrl(_ctx).GetTarget2(hash);
int a = 0;
foreach (Model.Data.DAO.Url i in t)
{
if (i != null)
{
a=a+1;
}
}
if (a==0)
{
return new TimeoutExceptionObjectResult(error: "Not Found",503);
}else
if (DateTime.Compare(DateTime.Now, t.ElementAt(0).ExpireAt) > 0)
{
t.ElementAt(0).state = "expire";
_ctx.Entry(t.ElementAt(0)).State = EntityState.Modified;
await _ctx.SaveChangesAsync();
return new TimeoutExceptionObjectResult(error: "Url expiré",501);
}
string url= t.ElementAt(0).UrlOrigin;
Console.Write(url);
return new Redirect(url);
}
the GetTarget2 method :
public async Task<IEnumerable<Url>> GetTarget2(string hash)
{
var t2 = await _ctx.Url.Where(u => u.UrlShort == hash).ToArrayAsync();
return t2;
}
and the entity :
[Table("Url")]
public class Url
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string UrlShort { get; set; }
public string UrlOrigin { get; set; }
public string state { get; set; }
public int Customer_id { get; set; }
public int? targetItemId { get; set; }
public DateTime CreatedAt { get; set; }
public DateTime ExpireAt { get; set; }
[JsonIgnore]
public List<Stats> GetStats { get; set; }
public Url()
{
}
public Url(int Id,string UrlShort,string UrlOrigin,string state,int Customer_id,DateTime CreatedAt,DateTime ExpireAt)
{
this.Id = Id;
this.UrlShort = UrlShort;
this.UrlOrigin = UrlOrigin;
this.state = state;
this.Customer_id = Customer_id;
this.CreatedAt = CreatedAt;
this.ExpireAt = ExpireAt;
}
}
when i try to pass a code which is in database i get this : Not found which means it does not find it in database
Update:
the database context declaration :
private readonly DatabaseContext _ctx;
and its definition :
public class DatabaseContext : DbContext
{
public DatabaseContext(DbContextOptions<DatabaseContext> options) : base(options)
{
}
public DbSet<Url> Url { get; set; }
public DbSet<User> User { get; set; }
public DbSet<Stats> Stats { get; set; }
}
If I understood your question correctly then you are trying to redirect to another action method by reading the URL from the database. If the retured Url belongs to your application and just have the dynamic parameter then you can try RedirectToAction method for the redirect.
Syntax:
return RedirectToAction("ActionName", new { id = 90 });
The will cause a redirect to YourApp/Controller/ActionName/90
All you have to create an action method with argument (if required) and redirect from another action method from where you are getting url.
Alternativly you can also use
return RedirectToAction("Index", new RouteValueDictionary(
new { controller = "NameOfController", action = "Index", id = id } )
);
The problem was the Redirect method which indicates its content was null or empty although i printed the url in terminal successfully. So What i did is adding a prefix (http://) like this Redirect("http://"+url), url is the url coming from the database. if the url already contains the prefix http or https i remove it before passing it as parameter inside Redirect method. Doing this solved my problem.
I am doing the backend according to the given architecture given by the company I recently started working.
I am new to C# and now I'm trying to do some post/get/put methods for some api-s.
There is a problem which I couldn't solve it.
Postman says:
{
"code": 1,
"message": "Unauthorize"
}
But the status code is 200 OK.
UserController.cs
[Route("v1/users")]
[Produces("application/json")]
public class UserController : BaseController
{
/// <summary>
/// Get list of users (Authorize)
/// </summary>
/// <returns>
/// </returns>
[ProducesResponseType(typeof(BaseResponseModel<List<UserResource>>), 200)]
[HttpGet]
public async Task<IActionResult> Get()
{
var user = await _userService.GetUserResourcesAsync();
return Success(user);
}
}
This doesn't make any sense, or am I so dumb to realise the problem?
I have a login method and I can login, I get the success code, then I do this:
enter image description here
Header
IProductService.cs
public interface IProductService
{
Task<ProductDto> GetAsync(int id);
}
ProductService.cs
public async Task<ProductDto> GetAsync(int id)
{
var product = await _context.Product.SingleOrDefaultAsync(p => p.Id == id);
return _mapper.Map<ProductDto>(product);
}
ProductDto.cs
public class ProductDto
{
public int Id { get; set; }
public CategoryDto CategoryId { get; set; }
public string Title { get; set; }
public bool AllowEdit { get; set; }
public string ItemCode { get; set; }
public string CustomerCode { get; set; }
}
Product.cs
[Table("Products")]
public class Product : DomainModel<int>
{
[Required]
public int ProductCategoryId { get; set; }
[ForeignKey("ProductCategoryId")]
public virtual ProductCategory ProductCategory { get; set; }
[Required, StringLength(256)]
public string Title { get; set; }
[Required, DefaultValue(false)]
public bool AllowEdit { get; set; }
[StringLength(50)]
public string ItemCode { get; set; }
[StringLength(50)]
public string CustomerCode { get; set; }
}
Using the ProducesResponseTypeAttribute, the attributed API actually defines what should be response code for specified types. See the definition
of at ProducesResponseTypeAttribute.
How it works
Take following example which shows that the API throws the 404 error if the object is null:
public IActionResult GetById(string id)
{
var post = <<Your logic here>>;
if (post == null)
{
return NotFound();
}
return Json(post);
}
Now the same method can be changed to following, which would return 404 by using the ProducesResponseType instead of the code is being written in your API logic.
[ProducesResponseType((int)HttpStatusCode.NotFound)]
public IActionResult GetPostById(string id)
{
var post = <<Your logic here>>;
return Json(post);
}
In cases, it might be good to also define a more explicit response type for successful calls. To do so, add a ProducesResponseTypeAttribute for status code with type. ( return type as parameter, which makes the Type property of Produces redundant).
This is valuable, if you want to return different things from one and the same method, for example, the following returns two different types depending on the returned status code:
[ProducesResponseType((int)HttpStatusCode.NotFound)]
[ProducesResponseType(typeof(Model), (int)HttpStatusCode.OK)]
public IActionResult GetById(string id)
What is your problem
Now if you see your code which defines this attribute as [ProducesResponseType(typeof(BaseResponseModel<List<UserResource>>), 200)]. And the code to fetch user :
var user = await _userService.GetUserResourcesAsync();
returns BaseResponseModel<T>. The BaseResponseModel should contain the Code and Message property. So here, the response returned by API is type of BaseResponseModel<T>, so API would return 200 HTTP Status as defined by attribute.
How to Fix
Either you return a different object in case of Unauthorize exception and attach the ProducesResponseType specific to that type OR handle the unathorized logic based at Authorize attribute.