ASP.net WebAPI using [FromUri] Attribute problems - asp.net-web-api

This's my controller code:
[HttpGet]
[Route()]
public async Task<List<GetCategoryTreeOutputDto>> GetDatasetCategoryTreeAsync([FromUri(Name = "")] GetCategoryTreeInputDto input)
{
return await _category.GetDatasetCategoryTreeAsync(input);
}
public class GetCategoryTreeInputDto
{
/// <summary>
/// 名称
/// </summary>
public string Name { get; set; }
public int? ParentId { get; set; }
}
and I get an error:
When I assign a parameter value,it works,So I have to give a value to use it?But these parameters don't always need to be assigned value.
i assigned value,it works

Related

Nullable Class as a Property is not Nullable. Why?

I have a class that I created where a property is another class I wrote. I am being warned that my property is non-nullable and I don't understand why. Aren't all properties of nullable classes nullable?
public class Yeast
{
public int? Id { get; set; }
public Code Brand { get; set; }
public Code Style { get; set; }
public string? Trademark { get; set; }
public int? TempMin { get; set; }
public int? TempMax { get; set; }
public double? Alcohol { get; set; }
public string? Note { get; set; }
}
/// <inheritdoc cref="ICode"/>
public class Code : ICode
{
/// <inheritdoc cref="ICode.Id"/>
public int? Id { get; set; }
/// <inheritdoc cref="ICode.ParentId"/>
public int? ParentId { get; set; }
/// <inheritdoc cref="ICode.Literal"/>
public string? Literal { get; set; }
/// <inheritdoc cref="ICode.Enabled"/>
public bool? Enabled { get; set; }
/// <inheritdoc cref="ICode.Description"/>
public string? Description { get; set; }
}
public interface ICode
{
/// <summary>
/// Code Unique Identifier
/// </summary>
int? Id { get; set; }
/// <summary>
/// Code Foreign Key
/// </summary>
/// <remarks>Optional</remarks>
int? ParentId { get; set; }
/// <summary>
/// Literal Name of Code
/// </summary>
string? Literal { get; set; }
/// <summary>
/// Description of Code
/// </summary>
string? Description { get; set; }
/// <summary>
/// Enabled for Use
/// </summary>
bool? Enabled { get; set; }
}
The issue here is that if you do new Yeast().Brand.Id you'll get a NullReferenceException because Brand is null - but you declared it wouldn't be.
So either the constructor needs to set Brand to a non-null Code value, or you need to mark it as nullable so the compiler can flag it in the consuming code (i.e. say that new Yeast().Brand needs to handle the possibility of a null value).

Web Api take an array from front

The question is very simple :-).
I'm a beginner.
Data comes to the controller (WebApi).
If you put an object.
[HttpPost]
public async Task<IActionResult> AddOrder(Object [] orderR)
ValueKind = Object : "{"product":{"id":"72ae28f2-4ad3-4e97-5a7b-
08d8db1aac26","code":"666666","name":"test6","price":6,"category":"test6","orrderItems":"320d57eb-
3333-45d2-f497-08d8d66a0d39"},"quality":1}"
ValueKind = Object : "{"product":{"id":"72ae28f2-4ad3-4e97-5a7b-
08d8db1aac26","code":"666666","name":"test6","price":6,"category":"test6","orrderItems":"320d57eb-
3333-45d2-f497-08d8d66a0d39"},"quality":1}"
ValueKind = Object : "{"product":{"id":"72ae28f2-4ad3-4e97-5a7b-
08d8db1aac26","code":"666666","name":"test6","price":6,"category":"test6","orrderItems":"320d57eb-
3333-45d2-f497-08d8d66a0d39"},"quality":1}"
I created a class.
public class OrderR
{
public Guid ID { get; set; }
public Guid orrderItems { get; set; }
public string CODE { get; set; }
public string NAME { get; set; }
public int PRICE { get; set; }
public string CATEGORY { get; set; }
}
I am trying to get an array.
[HttpPost]
public async Task<IActionResult> AddOrder(OrderR[] orderR)
But get null.
What am I doing wrong?
How is it correct?
Create new class:
public class Order
{
public OrderR Product { get; set; }
public int Quality { get; set; }
}
and change your action to this:
[HttpPost]
public async Task<IActionResult> AddOrder(Order[] orders)
{
.... your code
}
it was tested in Postman using this data:
[
{"product":{"id":"72ae28f2-4ad3-4e97-5a7b-08d8db1aac26","code":"666666","name":"test1","price":6,"category":"test6","orrderItems":"320d57eb-3333-45d2-f497-08d8d66a0d39"},"quality":1},
{"product":{"id":"72ae28f2-4ad3-4e97-5a7b-08d8db1aac26","code":"666666","name":"test2","price":6,"category":"test6","orrderItems":"320d57eb-3333-45d2-f497-08d8d66a0d39"},"quality":1},
{"product":{"id":"72ae28f2-4ad3-4e97-5a7b-08d8db1aac26","code":"666666","name":"test3","price":6,"category":"test6","orrderItems":"320d57eb-3333-45d2-f497-08d8d66a0d39"},"quality":1}
]
and everything works fine. If you want to test it in Postman too, add [FromBody] to the action input parameter:
[HttpPost]
public async Task<IActionResult> AddOrder([FromBody] Order[] orders)
{
return Ok(orders);
}

OutputCache varying by a complex object property

I have a controller action that receives a complex object as a parameter, I need the OutputCache to vary by one of the properties of this complex object. Is this possible? How?
if you have a model like
public class person{
public string Name {get;set;}
public string location {get;set;}
}
and in the (strongly typed)view you have a form
#model Person
#Html.BeginForm(){
#Html.TextBoxFor(x=>x.Name)
#Html.TextBoxFor(x=>x.location)
}
and you submit the form to an ActionResult savePerson, with varying signature like
public ActionResult savePerson(Person p){
// p.Name
// p.location
}
or
public ActionResult savePerson(string Name, string location){
}
therefore i think if you annotate the ActionResult like
[OutputCache(Duration=3600, VaryByParam="Name")]
public ActionResult savePerson(Person p)
{
//
return View();
}
it will do for you, or if you have a complex model like
public class person{
public string Name {get;set;}
public Location loc {get;set;}
}
public class Location{
public string address
}
try
[OutputCache(Duration=3600, VaryByParam="Person.Location.address")]
public ActionResult savePerson(Person p)
{
//
return View();
}
I had the same requirement as above and came up with a slightly different approach
The class
/// <summary>
/// This class is used to encapsulate search filters for monitor graphs
/// </summary>
public class DatacarMonitorSearchCriteriaModel
{
public int? SynergyCode { get; set; }
[Required]
[DataType(DataType.Date)]
public DateTime StartDate { get; set; }
[Required]
[DataType(DataType.Date)]
public DateTime EndDate { get; set; }
/// <summary>
/// Filter to apply
/// </summary>
public IEnumerable<int> Countries { get; set; }
public DatacarMonitorSearchCriteriaModel()
{
Countries = new List<int>();
}
}
OutputCacheComplexAttribute
/// <summary>
/// <para>
/// An instance of this class mimic the behaviour of OutputCacheAttribute but for complex objects.
/// </para>
/// <para>
/// It allows to cache the output of any action that takes complex objects
/// </para>
/// </summary>
public class OutputCacheComplexAttribute : OutputCacheAttribute
{
private readonly Type[] _types;
private string _cachedKey;
/// <summary>
/// Initializes a new instance of the <see cref="OutputCacheComplexAttribute"/> class.
/// </summary>
/// <param name="types">Types that this attribute will lookup for in QueryString/Form data and store values in cache.</param>
/// <exception cref="System.ArgumentOutOfRangeException">type;type cannot be null</exception>
public OutputCacheComplexAttribute(params Type[] types)
{
if (types == null)
{
throw new ArgumentOutOfRangeException("type", "type cannot be null");
}
_types = types;
}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
StringBuilder sbCachedKey = new StringBuilder();
if (filterContext.HttpContext.Request.Url != null)
{
string path = filterContext.HttpContext.Request.Url.PathAndQuery;
IDictionary<string, object> parameters = filterContext.ActionParameters;
//we need to compute a cache key which will be used to store the action output for later retrieval
//The cache key scheme is
// {url}:{key 1}:{value};[{key 2}:{value 2}[; ... {key n}:{value n}]];
// where :
// - url is the url of the action that will be executed
// - key n is the name of the n-th parameter
// - value n is the value of the n-th parameter as json string.
foreach (KeyValuePair<string, object> kv in parameters)
{
var kv1 = kv;
if (kv.Value != null && _types.AtLeastOnce(t => t.IsInstanceOfType(kv1.Value)))
{
sbCachedKey = sbCachedKey.AppendFormat("{0}:{1};",kv.Key,
JsonConvert.SerializeObject(kv.Value, Formatting.None, new JsonSerializerSettings()
{
NullValueHandling = NullValueHandling.Ignore,
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
}));
}
}
_cachedKey = String.Format("{0}:{1}:{2}", GetType().Name, path, sbCachedKey.ToString());
}
if (!String.IsNullOrWhiteSpace(_cachedKey) && filterContext.HttpContext.Cache[_cachedKey] != null)
{
filterContext.Result = (ActionResult)filterContext.HttpContext.Cache[_cachedKey];
}
else
{
base.OnActionExecuting(filterContext);
}
}
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
if (!String.IsNullOrWhiteSpace(_cachedKey))
{
filterContext.HttpContext.Cache.Add(_cachedKey, filterContext.Result, null,
DateTime.UtcNow.AddSeconds(Duration), Cache.NoSlidingExpiration,
CacheItemPriority.Default, null);
}
base.OnActionExecuted(filterContext);
}
}
Attribute usage
[OutputCacheComplex(typeof(DatacarMonitorSearchCriteriaModel), Duration = OutputCacheDurationInSeconds, Location = OutputCacheLocation.Server)]
public async Task<JsonNetResult<DatacarMonitorDetailModel>> ReadMonitorDetailsJson([DataSourceRequest] DataSourceRequest request, DatacarMonitorSearchCriteriaModel criteria)
{
//some really complicated code here
}
with this new attribute, you can specify which type[s] to use for caching and the cache key will be computed based on values of each its properties.
For object, just that work fine:
[OutputCache(VaryByParam = "*", Duration = 60)]

ASP.NET Web API avoid invalid parameters in query string

Given the following Web API controller action:
// GET api/values
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
Executing the following request is not failing, even when the parameter in the query string does not exist:
http://localhost:22297/api/values?someinvalidparameter=10
Is there a way to ensure that all parameters in the query string are valid parameters for the action that's being invoked?
You can write an action filter that validates that all the query parameters are there in the action parameters and throws if not.
using System;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http.Controllers;
using System.Web.Http.Filters;
namespace My.Namespace.Filters
{
/// <summary>
/// Action filter that checks that parameters passed in the query string
/// are only those that we specified in methods signatures.
/// Otherwise returns 404 Bad Request.
/// </summary>
public class ValidateQueryParametersAttribute : ActionFilterAttribute
{
/// <summary>
/// This method runs before every WS invocation
/// </summary>
/// <param name="actionContext"></param>
public override void OnActionExecuting(HttpActionContext actionContext)
{
//check that client does not use any invalid parameter
//but just those that are required by WS methods
var parameters = actionContext.ActionDescriptor.GetParameters();
var queryParameters = actionContext.Request.GetQueryNameValuePairs();
if (queryParameters.Select(kvp => kvp.Key).Any(queryParameter => !parameters.Any(p => p.ParameterName == queryParameter)))
{
actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.BadRequest);
}
}
}
}
In order for that to work with out of the box validation support nicely, I created my own action selector which makes it possible to bind URI parameters to complex type objects without duplication.
So, you can do the following with this action selector:
public class CarsByCategoryRequestCommand {
public int CategoryId { get; set; }
public int Page { get; set; }
[Range(1, 50)]
public int Take { get; set; }
}
public class CarsByColorRequestCommand {
public int ColorId { get; set; }
public int Page { get; set; }
[Range(1, 50)]
public int Take { get; set; }
}
[InvalidModelStateFilter]
public class CarsController : ApiController {
public string[] GetCarsByCategoryId(
[FromUri]CarsByCategoryRequestCommand cmd) {
return new[] {
"Car 1",
"Car 2",
"Car 3"
};
}
public string[] GetCarsByColorId(
[FromUri]CarsByColorRequestCommand cmd) {
return new[] {
"Car 1",
"Car 2"
};
}
}
Then, you can register an action filter to validate the user inputs to terminate request and return back a "400 Bad Request" response along with the validation error messages:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class InvalidModelStateFilterAttribute : ActionFilterAttribute {
public override void OnActionExecuting(HttpActionContext actionContext) {
if (!actionContext.ModelState.IsValid) {
actionContext.Response = actionContext.Request.CreateErrorResponse(
HttpStatusCode.BadRequest, actionContext.ModelState);
}
}
}
Check out the below posts for more information about this action selector and how you can get it:
Complex Type Action Parameters with ComplexTypeAwareActionSelector in ASP.NET Web API - Part 1
Complex Type Action Parameters with ComplexTypeAwareActionSelector in ASP.NET Web API - Part 2
For .net core web-api it will be a little different:
public class ValidateQueryParametersAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext actionContext)
{
var parameters = actionContext.ActionDescriptor.Parameters.ToList();
var queryParameters = actionContext.HttpContext.Request.Query.Keys.ToList();
if (queryParameters.Any(queryParameter => !parameters.Any(p => p.Name == queryParameter)))
{
actionContext.Result = new JsonResult(new { HttpStatusCode.BadRequest });
}
}
}

nop commerce automapper exception, missing type map configuration or unsupported mapping

I have tried to debug and find where the mismatch is coming from but I can not. Any ideas about where to look?
here is the model
public class PatientModel : BaseNopEntityModel
{
public PatientModel()
{
AvailableStates = new List<SelectListItem>();
}
[NopResourceDisplayName("Patient.Fields.FirstName")]
[AllowHtml]
public string FirstName { get; set; }
[NopResourceDisplayName("Patient.Fields.LastName")]
[AllowHtml]
public string LastName { get; set; }
[NopResourceDisplayName("Patient.Fields.MiddleName")]
[AllowHtml]
public string MiddleName { get; set; }
[NopResourceDisplayName("Patient.Fields.RoomNumber")]
[AllowHtml]
public string RoomNumber { get; set; }
[NopResourceDisplayName("Patient.Fields.HospitalName")]
[AllowHtml]
public string HospitalName { get; set; }
[NopResourceDisplayName("Patient.Fields.StateProvince")]
public int? StateProvinceId { get; set; }
[NopResourceDisplayName("Patient.Fields.StateProvince")]
[AllowHtml]
public string StateProvince { get; set; }
[NopResourceDisplayName("Patient.Fields.City")]
[AllowHtml]
public string City { get; set; }
[NopResourceDisplayName("Patient.Fields.ZipPostalCode")]
[AllowHtml]
public string ZipPostalCode { get; set; }
public IList<SelectListItem> AvailableStates { get; set; }
public bool FirstNameDisabled { get; set; }
public bool LastNameDisabled { get; set; }
public bool MiddleNameDisabled { get; set; }
public bool RoomNumberDisabled { get; set; }
public bool HospitalNameDisabled { get; set; }
public bool StateProvinceDisabled { get; set; }
public bool CityDisabled { get; set; }
public bool ZipPostalCodeDisabled { get; set; }
}
and here is the entity that it is trying to map to
public class Patient : BaseEntity, ICloneable
{
/// <summary>
/// Gets or sets the first name
/// </summary>
public virtual string FirstName { get; set; }
/// <summary>
/// Gets or sets the last name
/// </summary>
public virtual string LastName { get; set; }
/// <summary>
/// Gets or sets the middle name
/// </summary>
public virtual string MiddleName { get; set; }
/// <summary>
/// Gets or sets the patient room number
/// </summary>
public virtual string RoomNumber { get; set; }
public virtual string HospitalName { get; set; }
/// <summary>
/// Gets or sets the state/province identifier
/// </summary>
public virtual int? StateProvinceId { get; set; }
/// <summary>
/// Gets or sets the state/province
/// </summary>
public virtual StateProvince StateProvince { get; set; }
/// <summary>
/// Gets or sets the city
/// </summary>
public virtual string City { get; set; }
/// <summary>
/// Gets or sets the zip/postal code
/// </summary>
public virtual string ZipPostalCode { get; set; }
public virtual DateTime CreatedOnUtc { get; set; }
public object Clone()
{
var pat = new Patient()
{
FirstName = this.FirstName,
LastName = this.LastName,
MiddleName = this.MiddleName,
RoomNumber = this.RoomNumber,
HospitalName = this.HospitalName,
StateProvince = this.StateProvince,
StateProvinceId = this.StateProvinceId,
City = this.City,
ZipPostalCode = this.ZipPostalCode,
CreatedOnUtc = DateTime.UtcNow
};
return pat;
}
}
and mapper where the issue occurs
public static PatientModel ToModel(this Patient entity)
{
return Mapper.Map<Patient, PatientModel>(entity);
}
That means you never called Mapper.CreateMap<>() for those two types.
I found a way of getting it to work correctly the only problem was I had to change
public static PatientModel ToModel(this Patient entity)
{
return Mapper.Map<Patient, PatientModel>(entity);
}
and remove the mapper and do it manually to map the patient entitiy to the model.

Resources