ASP.Net API how to let API accept an IFromFile and an object at the same API call - asp.net

I have the following API that works properly
[HttpPost("testing")]
public string Testing(IFormFile file, string str, int num)
{
return str + num.ToString();
}
What I want to do ideally is to pass "str" and "num" in an object instead of each param on its own
Like this:
public class Testdto
{
public int Num{ get; set; }
public string Str { get; set; }
}
[HttpPost("testing")]
public string Testing(IFormFile file, Testdto dto)
{
return dto.Str + dto.Num.ToString();
}
Of course the above format rends an error, it does not work.
Is there a way to make it work? My real API body is quite large and contains nested objects so I can't just pass them all as params in the API

add the FromForm Attribute :
[HttpPost("testing")]
public string Testing(IFormFile file, [FromForm]testdto dto)
{
return dto.str + dto.num.ToString();
}
And add the parameters to request form
The result:

You can also create class having properties as IFormFile and other fileds.
And pass it to your controller with [FromForm] attribute
Sample Code:
[HttpPost]
[Route("FileUpload")]
public ActionResult FileUploaded([FromForm] PostRequest postRequest)
{
return Ok(postRequest.str);
}
public class PostRequest
{
public int Num { get; set; }
public string Str { get; set; }
public IFormFile File { get; set; }
}

Related

Clean way for updating object in a collection of abstract objects

As I'm developping an asp net core + ef core 2.0 with localized objects in my model, I adapted the solution provided in the following link to localize my objects link.
I'm now trying to find a clean way to update my collection of translation when updated object are received in the controller.
For the moment I have a step model class defined this way :
public class Step
{
//Native properties
public Guid ID { get; set; }
public string Name { get; set; }
public int Order { get; set; }
public string ScriptBlock { get; set; }
//Parent Step Navigation property
public Nullable<Guid> ParentStepID { get; set; }
public virtual Step ParentStep { get; set; }
//Collection of sub steps
public virtual ICollection<Step> SubSteps { get; set; }
//MUI Properties
public TranslationCollection<StepTranslation> Translations { get; set; }
public string Description { get; set; }
//{
// get { return Translations[CultureInfo.CurrentCulture].Description; }
// set { Translations[CultureInfo.CurrentCulture].Description = value; }
//}
public Step()
{
//ID = Guid.NewGuid();
Translations = new TranslationCollection<StepTranslation>();
}
}
public class StepTranslation : Translation<StepTranslation>
{
public Guid StepTranslationId { get; set; }
public string Description { get; set; }
public StepTranslation()
{
StepTranslationId = Guid.NewGuid();
}
}
Translation and translationCollection are the same as in the link
public class TranslationCollection<T> : Collection<T> where T : Translation<T>, new()
{
public T this[CultureInfo culture]
{
// indexer
}
public T this[string culture]
{
//indexer
}
public bool HasCulture(string culture)
{
return this.Any(x => x.CultureName == culture);
}
public bool HasCulture(CultureInfo culture)
{
return this.Any(x => x.CultureName == culture.Name);
}
}
public abstract class Translation<T> where T : Translation<T>, new()
{
public Guid Id { get; set; }
public string CultureName { get; set; }
protected Translation()
{
Id = Guid.NewGuid();
}
public bool HasProperty(string name)
{
return this.GetType()
.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Any(p => p.Name == name);
}
}
My issue in this sample is how to deal correctly with the PUT method and the Description property of my step controller. When it receive a Step object to update (which is done through a native c# client) only the string Description property of Step might have been created/updated/unchanged. So I have to update/create/do Nothing on the Description of the translation in the correct culture.
My first guess is to add in the TranslationCollection class a method in which I could pass the culture, the name of the property to update or not (Description in this case) and the value of the Description.
But as the TranslationCollection is a collection of abstract objects I don't even if this is a good idea and if it's possible.
If someone would have any advice on it (hoping I was clear enough) it would be great !
Finally answered my own question, and it was quite simple.
Just had to use the indexer like :
myobject.Translations[userLang].Name = value;

How to overload an Odata V2 controller method to accept multiple values as parameters?

I am new in ASP.NET MVC web development. I am just trying to overload a simple odata controller method for several days but failing again and again. I want to know the mystery behind this. Please help me...
This is my EducationInfo Model class...
public partial class EducationInfo
{
[Key]
public int EducationID { get; set; }
[ForeignKey("UserID")]
public int UserID { get; set; }
[Required]
public string Title { get; set; }
[Required]
[StringLength(50)]
public string EducationLevel { get; set; }
public string Department_Group { get; set; }
public string InstituteName { get; set; }
[Required]
public string Board_University_Name { get; set; }
[StringLength(30)]
public string Duration { get; set; }
public DateTime PassingYear { get; set; }
[StringLength(30)]
public string Result { get; set; }
public virtual UserInfo UserInfo { get; set; }
}
And here is one of my EducationInfoesController GET methods which accepts EducationID as parameter
// GET: odata/EducationInfoes(5)
[EnableQuery]
public SingleResult<EducationInfo> GetEducationInfo([FromODataUri] int key)
{
return SingleResult.Create(db.EducationInfoes.Where(educationInfo => educationInfo.EducationID == key));
}
I want to overload this method in a such way that it might take 2 parameters [e.g. GetEducationInfo(int UserID, string EducationLevel)] and return only a single result based on the combination of two parameters (UserID and EducationLevel).
I have already tried to overload this method by following code...
// GET: odata/EducationInfoes(5, "bachelor")
[EnableQuery]
public SingleResult<EducationInfo> GetEducationInfo([FromODataUri] int key, string eLevel)
{
return SingleResult.Create(db.EducationInfoes.Where(educationInfo => educationInfo.UserID == key && educationInfo.EducationLevel == eLevel));
}
But when I'm sending GET requst to my WebService by this URL http://localhost:10194/odata/EducationInfoes(5, "Bachelor"), I'm getting this message:
No HTTP resource was found that matches the request URI 'http://localhost:10194/odata/EducationInfoes(5,"Bachelor")'.
If I change the default method to the following...
// GET: odata/EducationInfoes(5)
[EnableQuery]
public SingleResult<EducationInfo> GetEducationInfo([FromODataUri] int key)
{
return SingleResult.Create(db.EducationInfoes.Where(educationInfo => educationInfo.UserId== key));
}
and requesting using this url http://localhost:10194/odata/EducationInfoes(3) and getting this message
The action 'GetEducationInfo' on controller 'EducationInfoes' returned a SingleResult containing more than one element. SingleResult must have zero or one elements.
this message is returned because every single user has multiple Educational Information stored in EducationInfo table.
But I must have to get every EducationInfo result separately or as single result based on UserID and EducationLevel but not based on EducationID. Please help me...

Using DTO's with OData & Web API

Using Web API and OData, I have a service which exposes Data Transfer Objects instead of the Entity Framework entities.
I use AutoMapper to transform the EF Entities into their DTO counter parts using ProjectTo():
public class SalesOrdersController : ODataController
{
private DbContext _DbContext;
public SalesOrdersController(DbContext context)
{
_DbContext = context;
}
[EnableQuery]
public IQueryable<SalesOrderDto> Get(ODataQueryOptions<SalesOrderDto> queryOptions)
{
return _DbContext.SalesOrders.ProjectTo<SalesOrderDto>(AutoMapperConfig.Config);
}
[EnableQuery]
public IQueryable<SalesOrderDto> Get([FromODataUri] string key, ODataQueryOptions<SalesOrderDto> queryOptions)
{
return _DbContext.SalesOrders.Where(so => so.SalesOrderNumber == key)
.ProjectTo<SalesOrderDto>(AutoMapperConfig.Config);
}
}
AutoMapper (V4.2.1) is configured as follows, note the ExplicitExpansion() which prevents serialisation auto expanding navigation properties when they are not requested:
cfg.CreateMap<SalesOrderHeader, SalesOrderDto>()
.ForMember(dest => dest.SalesOrderLines, opt => opt.ExplicitExpansion());
cfg.CreateMap<SalesOrderLine, SalesOrderLineDto>()
.ForMember(dest => dest.MasterStockRecord, opt => opt.ExplicitExpansion())
.ForMember(dest => dest.SalesOrderHeader, opt => opt.ExplicitExpansion());
ExplicitExpansion() then creates a new problem where the following request throws an error:
/odatademo/SalesOrders('123456')?$expand=SalesOrderLines
The query specified in the URI is not valid. The specified type member 'SalesOrderLines' is not supported in LINQ to Entities
The navigation property SalesOrderLines is unknown to EF so this error is pretty much what I expected to happen. The question is, how do I handle this type of request?
The ProjectTo() method does have an overload that allows me to pass in an array of properties that require expansion, I found & modified the extension method ToNavigationPropertyArray to try and parse the request into a string array:
[EnableQuery]
public IQueryable<SalesOrderDto> Get([FromODataUri] string key, ODataQueryOptions<SalesOrderDto> queryOptions)
{
return _DbContext.SalesOrders.Where(so => so.SalesOrderNumber == key)
.ProjectTo<SalesOrderDto>(AutoMapperConfig.Config, null, queryOptions.ToNavigationPropertyArray());
}
public static string[] ToNavigationPropertyArray(this ODataQueryOptions source)
{
if (source == null) { return new string[]{}; }
var expandProperties = string.IsNullOrWhiteSpace(source.SelectExpand?.RawExpand) ? new List<string>().ToArray() : source.SelectExpand.RawExpand.Split(',');
for (var expandIndex = 0; expandIndex < expandProperties.Length; expandIndex++)
{
// Need to transform the odata syntax for expanding properties to something EF will understand:
// OData may pass something in this form: "SalesOrderLines($expand=MasterStockRecord)";
// But EF wants it like this: "SalesOrderLines.MasterStockRecord";
expandProperties[expandIndex] = expandProperties[expandIndex].Replace(" ", "");
expandProperties[expandIndex] = expandProperties[expandIndex].Replace("($expand=", ".");
expandProperties[expandIndex] = expandProperties[expandIndex].Replace(")", "");
}
var selectProperties = source.SelectExpand == null || string.IsNullOrWhiteSpace(source.SelectExpand.RawSelect) ? new List<string>().ToArray() : source.SelectExpand.RawSelect.Split(',');
//Now do the same for Select (incomplete)
var propertiesToExpand = expandProperties.Union(selectProperties).ToArray();
return propertiesToExpand;
}
This works for expand, so now I can handle a request like the following:
/odatademo/SalesOrders('123456')?$expand=SalesOrderLines
or a more complicated request like:
/odatademo/SalesOrders('123456')?$expand=SalesOrderLines($expand=MasterStockRecord)
However, more complicated request that try to combine $select with $expand will fail:
/odatademo/SalesOrders('123456')?$expand=SalesOrderLines($select=OrderQuantity)
Sequence contains no elements
So, the question is: am I approaching this the right way?
It feels very smelly that I would have to write something to parse and transform the ODataQueryOptions into something EF can understand.
It seems this is a rather popular topic:
odata-expand-dtos-and-entity-framework
how-to-specify-the-shape-of-results-with-webapi2-odata-with-expand
web-api-queryable-how-to-apply-automapper
how-do-i-map-an-odata-query-against-a-dto-to-another-entity
While most of these suggest using ProjectTo, none seem to address serialisation auto expanding properties, or how to handle expansion if ExplictExpansion has been configured.
Classes and Config below:
Entity Framework (V6.1.3) entities:
public class SalesOrderHeader
{
public string SalesOrderNumber { get; set; }
public string Alpha { get; set; }
public string Customer { get; set; }
public string Status { get; set; }
public virtual ICollection<SalesOrderLine> SalesOrderLines { get; set; }
}
public class SalesOrderLine
{
public string SalesOrderNumber { get; set; }
public string OrderLineNumber { get; set; }
public string Product { get; set; }
public string Description { get; set; }
public decimal OrderQuantity { get; set; }
public virtual SalesOrderHeader SalesOrderHeader { get; set; }
public virtual MasterStockRecord MasterStockRecord { get; set; }
}
public class MasterStockRecord
{
public string ProductCode { get; set; }
public string Description { get; set; }
public decimal Quantity { get; set; }
}
OData (V6.13.0) Data Transfer Objects:
public class SalesOrderDto
{
[Key]
public string SalesOrderNumber { get; set; }
public string Customer { get; set; }
public string Status { get; set; }
public virtual ICollection<SalesOrderLineDto> SalesOrderLines { get; set; }
}
public class SalesOrderLineDto
{
[Key]
[ForeignKey("SalesOrderHeader")]
public string SalesOrderNumber { get; set; }
[Key]
public string OrderLineNumber { get; set; }
public string LineType { get; set; }
public string Product { get; set; }
public string Description { get; set; }
public decimal OrderQuantity { get; set; }
public virtual SalesOrderDto SalesOrderHeader { get; set; }
public virtual StockDto MasterStockRecord { get; set; }
}
public class StockDto
{
[Key]
public string StockCode { get; set; }
public string Description { get; set; }
public decimal Quantity { get; set; }
}
OData Config:
var builder = new ODataConventionModelBuilder();
builder.EntitySet<StockDto>("Stock");
builder.EntitySet<SalesOrderDto>("SalesOrders");
builder.EntitySet<SalesOrderLineDto>("SalesOrderLines");
I have created an Automapper explicit navigation expansion utility function that should work with N-deph expands. Posting it here since it might help someone.
public List<string> ProcessExpands(IEnumerable<SelectItem> items, string parentNavPath="")
{
var expandedPropsList = new List<String>();
if (items == null) return expandedPropsList;
foreach (var selectItem in items)
{
if (selectItem is ExpandedNavigationSelectItem)
{
var expandItem = selectItem as ExpandedNavigationSelectItem;
var navProperty = expandItem.PathToNavigationProperty?.FirstSegment?.Identifier;
expandedPropsList.Add($"{parentNavPath}{navProperty}");
//go recursively to subproperties
var subExpandList = ProcessExpands(expandItem?.SelectAndExpand?.SelectedItems, $"{parentNavPath}{navProperty}.");
expandedPropsList = expandedPropsList.Concat(subExpandList).ToList();
}
}
return expandedPropsList;
}
You can call it with :
var navExp = ProcessExpands(options?.SelectExpand?.SelectExpandClause?.SelectedItems)
it will return a list with ["Parent" ,"Parent.Child"]
I never really managed to work this one out. The ToNavigationPropertyArray() extension method helps a little, but does not handle infinite depth navigation.
The real solution is to create Actions or Functions to allow clients to request data requiring a more complicated query.
The other alternative is to make multiple smaller/simple calls then aggregate the data on the client, but this isn't really ideal.
When you want to mark something for explicit expansion in AutoMapper, you need to also opt-back-in when calling ProjectTo<>().
// map
cfg.CreateMap<SalesOrderHeader, SalesOrderDto>()
.ForMember(dest => dest.SalesOrderLines, opt => opt.ExplicitExpansion());
// updated controller
[EnableQuery]
public IQueryable<SalesOrderDto> Get()
{
return _dbContext.SalesOrders
.ProjectTo<SalesOrderDto>(
AutoMapperConfig.Config,
so => so.SalesOrderLines,
// ... additional opt-ins
);
}
While the AutoMapper wiki does state this, the example is perhaps a little misleading by not including the paired ExplicitExpansion() call.
To control which members are expanded during projection, set ExplicitExpansion in the configuration and then pass in the members you want to explicitly expand:

Passing Object as an parameter in Web Api

In Web Api How can pass object as parameter
// GET api/values/{can be any serilizable object}
public string Get(object data)
{
return "value";
}
[Serializable]
public class RequestData
{
public string Id { get; set; }
public string Name { get; set; }
}
Your object will have to be sent in the request as JSON. [Serializable] is different kind of serialization. Here we talking either JSON or XML serialization and it is built-in
public HttpResponseMessage Get(RequestData requestData)
{
HttpResponseMessage retMsg;
// pack your message here, select serializer {json, xml}, etc
return respMessage;
}
// [Serializable] - not needed, good old POCO is fine
public class RequestData
{
public string Id { get; set; }
public string Name { get; set; }
}

ASP.NET web API casting http response to json array

My Code works fine when calling REST URL:
http://api.feedzilla.com/v1/categories.json
but when I call following URL I get error:
http://api.feedzilla.com/v1/categories/15/articles.json?count=36&since=2012-11-15&client_source=&order=relevance&title_only=0&
Error:
{"Cannot deserialize the current JSON object (e.g. {\"name\":\"value\"}) into type 'System.Collections.Generic.IEnumerable`1[Nitin.News.DAL.Resources.Article]' because the type requires a JSON array (e.g. [1,2,3]) to deserialize correctly.\r\nTo fix this error either change the JSON to a JSON array (e.g. [1,2,3]) or change the deserialized type so that it is a normal .NET type (e.g. not a primitive type like integer, not a collection type like an array or List<T>) that can be deserialized from a JSON object. JsonObjectAttribute can also be added to the type to force it to deserialize from a JSON object.\r\nPath 'articles', line 1, position 12."}
My Code is as follows:
public class Article
{
public string publish_date { get; set; }
public string source { get; set; }
public string source_url { get; set; }
public string summary { get; set; }
public string title { get; set; }
public string url { get; set; }
}
public IEnumerable<Article> Get()
{
HttpClient client = new HttpClient();
client.BaseAddress = new Uri("http://api.feedzilla.com/v1/");
//Add an Accept header for JSON format.
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
// call the REST method
HttpResponseMessage response = client.GetAsync("http://api.feedzilla.com/v1/categories/2/articles.json??count=36&since=2012-11-15&client_source=&order=relevance&title_only=0&").Result; // Blocking call!
if (response.IsSuccessStatusCode)
{
// Parse the response body. Blocking!
return response.Content.ReadAsAsync<IEnumerable<Article>>().Result;
//wont work
//string JSON =response.Content.ReadAsStringAsync().Result;
//return JsonConvert.DeserializeObject<IEnumerable<T>>(JSON);
}
else
{
throw new Exception(string.Format("Data access faild,{0} ({1}) method:{2}", (int)response.StatusCode, response.ReasonPhrase, MethodURL));
}
}
You need another level in your object hierachy... i.e. a root to contain the IEnumerable.
Runing the JSON into the tool at http://json2csharp.com/ generates the following proxy classes:
public class Enclosure
{
public int length { get; set; }
public string media_type { get; set; }
public string uri { get; set; }
}
public class Article
{
public string author { get; set; }
public string publish_date { get; set; }
public string source { get; set; }
public string source_url { get; set; }
public string summary { get; set; }
public string title { get; set; }
public string url { get; set; }
public List<Enclosure> enclosures { get; set; }
}
public class RootObject
{
public List<Article> articles { get; set; }
public string description { get; set; }
public string syndication_url { get; set; }
public string title { get; set; }
}
You just need to change your code to this then:
// Parse the response body. Blocking!
return response.Content.ReadAsAsync<RootObject>().Result.articles;
Obviously strip out any properties you dont need. Just thought I would show them all out of interest.

Resources