i have this controller method that return an array of objects
public async Task<ActionResult<List<AllClientsDataModelDb>>> GetAll()
{
var ReturnValue = new List<AllClientsDataModelDb>();
ReturnValue = await Clda.GetClients(new { cm = 1 });
return (ReturnValue);
}
here is the code of AllClientsDataModelDb class
public class AllClientsDataModelDb
{
public long IDCLIENT { get; set; }
public string CL_CODE { get; set; }
public string CL_NOM { get; set; }
public string CL_ADRESSE { get; set; }
public string CL_CODEPOS { get; set; }
public string CL_VILLE { get; set; } = null;
public int CL_ETATCOMPTE { get; set; }
public int CL_AlerteCompta { get; set; }
}
but the result of that method (in browser) does not respect the case sensitivity of the class properties
Example :
[{"idclient":1,"cL_CODE":"1","cL_NOM":"EUROPEQUIPEMENTMysql","cL_ADRESSE":"ModifSoft","cL_CODEPOS":"44","cL_VILLE":"STDENIS","cL_ETATCOMPTE":1,"cL_AlerteCompta":0},
{"idclient":2,"cL_CODE":"2","cL_NOM":"A UTOMATISMES-SERVICESzzzz","cL_ADRESSE":null,"cL_CODEPOS":"97420","cL_VILLE":"LEPORT","cL_ETATCOMPTE":1,"cL_AlerteCompta":0},
what i'm doing wrong ?
You need to create your own Json Profile Formatter by inheriting from JsonOutputFormatter.
public class PascalCaseJsonProfileFormatter : JsonOutputFormatter
{
public PascalCaseJsonProfileFormatter() : base(new JsonSerializerSettings { ContractResolver = new DefaultContractResolver() }, ArrayPool<char>.Shared)
{
SupportedMediaTypes.Clear();
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse
("application/json;profile=\"https://en.wikipedia.org/wiki/PascalCase\""));
}
}
Then modify your Startup.cs file's ConfigureServices Method like this.
services.AddMvc()
.AddMvcOptions(options =>
{
options.OutputFormatters.Add(new PascalCaseJsonProfileFormatter());
});
Try this, it should work.
Related
I'm using .NET Core and WebApi and I am trying to figure out what the url would look like to send an array of objects through.
For example
public class DataObject
{
public int id { get; set;}
public string name { get; set }
}
[HttpGet()]
public <ActionResult<string>> GetSomething(DataObject[] data))
{
//do something and return a string
}
what would the url look like to do this? Should I use FromQuery or FromRoute on data? On the HttpGet(), what should be in the parenthesis? "{data}" or something else?
Everything I can find so far has been on integer arrays or string arrays, but not complex arrays to a get call.
Update
Still not able to get this to work even though I'm sure the reply I have gotten should work. Here is some more code.
[Route("api/[controller]/[action]")]
[HttpGet()]
[ProducesResponseType((int)HttpStatusCode.BadRequest)]
[ProducesResponseType(typeof(GridResult), (int)HttpStatusCode.OK)]
public async Task<ActionResult<GridResult>> GetGridData<TFilter1, TFilter2, TItem1>
([FromQuery]string sessionID, [FromQuery] GridDetails details, [FromQuery] TFilter1[] TFilters1, [FromQuery] TFilter2[] TFilters2, [FromQuery] TItem1[] TSorts)
and finally the url that I have generated that throws a 404.
https://localhost:44366/api/grid/GetGridData/sessionID=598357390&details?NUMBER_OF_ROWS_FIRST_RETURNED=100&CURSOR_POSITION=0&RESULT_IN_SAXORDER=false&TERSERESPONSE=true&IsStaticList=true&GRID_TYPE=list&REQUEST_TYPE=LIST.DATA_ONLY.STORED&GRID_NAME=WUWP09&TFilters1[0].AliasName=PRO_CODE&TFilters1[0].Operator=%3D&TFilters1[0].SEQNUM=1&TFilters1[1].AliasName=APR_CLASS&TFilters1[1].Operator=%3D&Tsorts[1].SEQNUM=2&Tsorts[0].ALIAS_NAME=pvd_value&Tsorts[0].TYPE=ASC
Update 2
https://localhost:44366/api/grid/GetGridData?sessionID=598357390&details.NUMBER_OF_ROWS_FIRST_RETURNED=100&details.CURSOR_POSITION=0&details.RESULT_IN_SAXORDER=false&details.TERSERESPONSE=true&details.IsStaticList=true&details.GRID_TYPE=list&details.REQUEST_TYPE=LIST.DATA_ONLY.STORED&details.GRID_NAME=WUWP09&details.TAB_NAME&details.LOCALIZE_RESULT&details.USER_FUNCTION_NAME&details.TOTALRECORDS&details.RES_IsMoreRecords&details.RES_CURRENT_CURSOR_POSITION&TFilters1[0].AliasName=PRO_CODE&TFilters1[0].Operator=%3D&TFilters1[0].SEQNUM=1&TFilters1[1].AliasName=APR_CLASS&TFilters1[1].Operator=%3D&Tsorts[1].SEQNUM=2&Tsorts[0].ALIAS_NAME=pvd_value&Tsorts[0].TYPE=ASC
Update 3
Startup.cs
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
var _accessor = services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
var config = new GridServices.Models.config();
Configuration.Bind("Connections", config);
services.AddSingleton(config);
services.AddSingleton(new Controllers.GridController(config));
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseMvc();
}
}
GridController
namespace EAMWebApi.Controllers
{
[Route("api/[controller]/[action]")]
[ApiController]
public class GridController : ControllerBase
{
config Config { get; }
//private readonly LinkGenerator _linkGenerator;
public GridController(config config)
{
config = Config;
//_linkGenerator = linkGenerator;
}
[HttpGet()]
[ProducesResponseType((int)HttpStatusCode.BadRequest)]
[ProducesResponseType(typeof(GridResult), (int)HttpStatusCode.OK)]
public async Task<ActionResult<GridResult>> GetGridData<TFilter1, TFilter2, TItem1>
([FromQuery]string sessionID, [FromQuery] GridDetails details, [FromQuery] TFilter1[] TFilters1 = null, [FromQuery] TFilter2[] TFilters2 = null, [FromQuery] TItem1[] TSorts = null)
{//Do something}
}
GridDetails
namespace GridServices.Models
{
public class GridDetails
{
public string GRID_NAME { get; set; }
public string NUMBER_OF_ROWS_FIRST_RETURNED { get; set; }
public string CURSOR_POSITION { get; set; }
public string TAB_NAME { get; set; }
public string RESULT_IN_SAXORDER { get; set; }
public string TERSERESPONSE { get; set; }
public string LOCALIZE_RESULT { get; set; }
public string USER_FUNCTION_NAME { get; set; }
public string TOTALRECORDS { get; set; }
public bool RES_IsMoreRecords { get; set; }
public bool IsStaticList { get; set; }
public string GRID_TYPE { get; set; }
public string REQUEST_TYPE { get; set; }
public string RES_CURRENT_CURSOR_POSITION { get; set; }
}
}
MultiAddOnFilter
public class MultiAddOnFilter
{
public string ALIAS_NAME { get; set; }
public string OPERATOR { get; set; }
public string OPERATORSpecified { get; set; }
public string VALUE { get; set; }
public string LPAREN { get; set; }
public string RPAREN { get; set; }
public string JOINER { get; set; }
public string JOINERSpecified { get; set; }
public string SEQNUM { get; set; }
public MultiAddOnFilter(string _ALIAS_NAME, string _OPERATOR, string _VALUE)
{
ALIAS_NAME = _ALIAS_NAME;
OPERATOR = _OPERATOR;
OPERATORSpecified = "true";
VALUE = _VALUE;
}
}
Sorts
namespace GridServices.Models
{
public class Sort
{
public string ALIAS_NAME { get; set; }
public string TYPE { get; set; }
public string TYPESpecified { get; set; }
public Sort(string _ALIAS_NAME, string _TYPE)
{
ALIAS_NAME = _ALIAS_NAME;
TYPE = _TYPE;
TYPESpecified = "true";
}
}
}
what would the url look like to do this?
It should be something like the following:
GET /Somecontroller/GetSomething?data[0].id=1&data[0].name=nameA&data[1].id=2&data[1].name=nameB&data[2].id=3&data[2].name=nameC
This payload is almost the same as you post in application/x-www-form-urlencoded format, except you'll send it as a querystring.
[Edit]
If one of those items were to be empty, would I have to pass %00 to it to indicate a null value?
Let's say you have such an object array:
data = [
{
"id": 1,
"name": "nameA"
},
{
"id": 2,
"name": null
},
{
"id": 3,
"name": "nameC"
}
]
Note the data[1].name==null. you don't have to specify data[1].name :
?data[0].id=1&data[0].name=nameA&data[1].id=2&data[2].id=3&data[2].name=nameC
If the whole data[1] item is null, just adjust the index of data[2] to data[1]:
data[0].id=1&data[0].name=nameA&data[1].id=3&data[1].name=name
Or you could add an empty field for this item:
?data[0].id=1&data[0].name=nameA&data[1].id=&data[2].id=3&data[2].name=nameC
What if the whole DataObject was null? /GetSomething?data=%00 ?
you don't have to specify /GetSomething?data=%00, just send a request to /GetSomething?, and then you'll get an empty array.
[Edit2]
There're two reasons that always routes you to a 404 result:
You're registering your GridController as a singleton. MVC will register controllers automatically (as a scoped service). Just remove that line :
services.AddSingleton(new Controllers.GridController(config));
Your controller action of GetGridData<TFilter1, TFilter2, TItem1> is a generic method. It won't work by default. There's already a thread on SO talking about this. I would also suggest you use a specific GridFilter type for each method. If you find yourself repeating the same logic, you could put your generic method into a parent MySupperGridBaseController<TFilter1, TFilter2, TItem1> class, something as below:
public class MySupperGridBaseController<TFilter1, TFilter2, TItem1> : ControllerBase
{
public async Task<ActionResult<GridResult>> GetGridData
([FromQuery]string sessionID, [FromQuery] GridDetails details, [FromQuery] TFilter1[] TFilters1 = null, [FromQuery] TFilter2[] TFilters2 = null, [FromQuery] TItem1[] TSorts = null)
{
...
}
}
// now we could reuse the same logic inherited from parent
public class GridController : MySupperGridBaseController<MultiAddOnFilter, MultiAddOnFilter, Sort>
{
}
I am trying to create a small asmx service with a method which will return some dummy data. I get the following error when I run the service:
"System.InvalidOperationException: System.Xml.XmlAttribute cannot be used as: 'xml element'."
My web method is as follows:
[WebMethod]
public SubmitCaseRequestResponse1 SubmitCaseRequest(SubmitCaseRequestRequest1 request)
{
var response = new SubmitCaseRequestResponse1
{
ResponseID = "456325898",
Success = true,
ValidationErrors = null
};
return response;
}
My SubmitCaseRequestResponse1 class:
public class SubmitCaseRequestResponse1
{
public string ResponseId { get; set; }
public bool Success { get; set; }
public ValidationError[] ValidationErrors { get; set; }
}
and the request class is :
public class SubmitCaseRequestRequest1
{
public AuthHeader AuthHeader { get; set; }
public SubmitCaseRequestRequest PostCaseDateRequest { get; set; }
}
I was supposed to serialize the complex type SubmitCaseRequestRequest1 and SubmitCaseRequestResponse1 . Needed to add [XmlElement] for the complex types and [XmlAttribute] for simple types
[System.Xml.Serialization.XmlTypeAttribute(Namespace = "http://tempuri.org/")]
[Serializable]
public class SubmitCaseRequestRequest1
{
[XmlElement]
public AuthHeader AuthHeader { get; set; }
[XmlElement]
public SubmitCaseRequestRequest PostCaseDateRequest { get; set; }
}
[Serializable]
public class SubmitCaseRequestRequest
{
[XmlElement]
public Guid? RequestId { get; set; }
[XmlAttribute]
public string LCICourtNumber { get; set; }
[XmlAttribute]
public string CaseNumber { get; set; }
[XmlAttribute]
public string DebtorLastName { get; set; }
[XmlAttribute]
public string DateType { get; set; }
}
public class OrderDTO
{
public string ClientName { get; set; }
public ICollection<OrderDetailDTO> Details { get; set; }
}
public class Order
{
public string ClientName { get; set; }
public ICollection<OrderDetail> Details { get; set; }
}
public class OrderDetailDTO
{
public int Quantity { get; set; }
public string ProductName { get; set; }
}
public class OrderDetail
{
public int OrderId { get; set; }
public int Quantity { get; set; }
public string ProductName { get; set; }
}
Let's say there are 4 OrderDetailDTO, I want to have the mapped OrderDetail instances with auto-incremented integer values. What I am doing now is post-process the mapped instance.
var mappedOrder = Mapper.Map<OrderDTO, Order>(orderDto);
var orderId = 1;
foreach (OrderDetail detail in mappedOrder.Details)
{
detail.OrderId = orderId++;
}
How can I configure the mapping options, so that the mapped ICollection<OrderDetail> contains 4 OrderDetail instances with OrderId as 1, 2, 3, 4?
You could configure AutoMapper to do this with AfterMap:
Mapper.CreateMap<OrderDTO, Order>()
.AfterMap((src, dest) =>
{
int orderId = 1;
foreach (OrderDetail detail in dest.Details)
{
detail.OrderId = orderId++;
}
});
I don't think there's really a "cleaner" way to do it using AutoMapper.
I use the following method which is much simpler and can be written in a base class or an extension method. The example here uses Generics but can be easily transformed
protected virtual IEnumerable<T> ConvertCsvLines(IEnumerable<TV> lines)
{
var lineNumber = 0;
return lines.Select(x =>
{
var retVal = Mapper.Map<TV, T>(x);
retVal.LineNumber = lineNumber++;
return retVal;
});
}
I used the FromJsonAttribute (created by Steve Sanderson), it's quite great, but sadly it doesn't pay attention to the AllowHtml attribute. I have the following model:
public class HKNewsPaperViewModel
{
public int Id { get; set; }
public string UserId { get; set; }
public string UserName { get; set; }
public string RPublisher { get; set; }
public string REditor { get; set; }
public string Title { get; set; }
public bool IsDraft { get; set; }
public bool IsNew { get; set; }
public List<HKNewsItemViewModel> NewsItems { get; set; }
public HKNewsPaperViewModel()
{
NewsItems = new List<HKNewsItemViewModel>();
}
}
public class HKNewsItemViewModel
{
public int Id { get; set; }
public string Title { get; set; }
public string Link { get; set; }
[AllowHtml]
public string Body { get; set; }
}
In my controller I receive data this way:
[HttpPost]
public ActionResult New([FromJson] HKNewsPaperViewModel model)
{
return View();
}
FromJson attribute looks like this:
public class FromJsonAttribute : CustomModelBinderAttribute
{
private readonly static JavaScriptSerializer serializer = new JavaScriptSerializer();
public override IModelBinder GetBinder()
{
return new JsonModelBinder();
}
private class JsonModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var stringified = controllerContext.HttpContext.Request[bindingContext.ModelName];
if (string.IsNullOrEmpty(stringified))
return null;
return serializer.Deserialize(stringified, bindingContext.ModelType);
}
}
}
My problem is that I can't pass html content where the AllowHtml attribute is there. Thanks a lot!
Could somebody please provide an example of how to combine two models within one view?
Currently I have a page called RecordCard which contains:
#model IEnumerable<WebApplication1.Models.Weight>
This is provided by the following code in the AccountController:
public ActionResult RecordCard()
{
var UserId = User.Identity.GetUserId();
var weightModel = from m in db.Weights where m.UserId == UserId select m;
return View(weightModel);
}
The RecordCard page also contains a form which is bound to the following class:
public class AddWeightModel
{
[Required]
[DataType(DataType.Text)]
[Display(Name = "Stone")]
public Nullable<short> Stone { get; set; }
[Required]
[DataType(DataType.Text)]
[Display(Name = "Pound")]
public Nullable<short> Pound { get; set; }
}
However, these are two individual models with different purposes, so how do I combine to a single model that contains an IEnumerable list and set of form elements that will ultimately post to the AccountController correctly to add a record to the database using the following code:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult RecordCard(Weight Model)
{
if (ModelState.IsValid)
{
using (WebApplication1Entities db = new WebApplication1Entities())
{
Weight weight = new Weight();
weight.UserId = User.Identity.GetUserId();
weight.Stone = Model.Stone;
weight.Pound = Model.Pound;
weight.Date = System.DateTime.Now;
db.Weights.Add(Model);
db.SaveChanges();
}
}
return View(Model);
}
I have included the Weight class below:
public partial class Weight
{
public int Id { get; set; }
public string UserId { get; set; }
public Nullable<short> Stone { get; set; }
public Nullable<short> Pound { get; set; }
public Nullable<System.DateTime> Date { get; set; }
}
Also here is the WebApplication1Entities class which declares the Weight table as Weights:
public partial class WebApplication1Entities : DbContext
{
public WebApplication1Entities()
: base("name=WebApplication1Entities")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
throw new UnintentionalCodeFirstException();
}
public virtual DbSet<Weight> Weights { get; set; }
}
Please explain what needs to be modified and how, no matter what I try to read, follow and implement, I seem to be missing something.
Any help would be much appreciated :-)
I would say this is good example of using ViewModel here. I would suggest something like -
Create ViewModel with the composition of the two classes
public class AddWeightModel
{
[Required]
[DataType(DataType.Text)]
[Display(Name = "Stone")]
public Nullable<short> Stone { get; set; }
[Required]
[DataType(DataType.Text)]
[Display(Name = "Pound")]
public Nullable<short> Pound { get; set; }
}
....
public partial class Weight
{
public int Id { get; set; }
public string UserId { get; set; }
public Nullable<short> Stone { get; set; }
public Nullable<short> Pound { get; set; }
public Nullable<System.DateTime> Date { get; set; }
}
.....
public class WeightViewModel
{
public IList<AddWeightModel> AddWeightModel { get; set; }
public Weight Weight { get; set; }
}
Then change your view to accept the view models -
#model WeightViewModel
Finally modify your controller to cope with the change -
public ActionResult RecordCard()
{
var UserId = User.Identity.GetUserId();
var weightModel = from m in db.Weights where m.UserId == UserId select m;
var viewModel = new WeightViewModel
{
Weight = weightModel,
AddWeightModel = new List<AddWeightModel>(){}
};
return View(viewModel);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult RecordCard(WeightViewModel viewModel)
{
Weight Model = viewModel.Weight;
if (ModelState.IsValid)
{
using (WebApplication1Entities db = new WebApplication1Entities())
{
Weight weight = new Weight();
weight.UserId = User.Identity.GetUserId();
weight.Stone = Model.Stone;
weight.Pound = Model.Pound;
weight.Date = System.DateTime.Now;
db.Weights.Add(Model);
db.SaveChanges();
}
}
return RedirectToAction("RecordCard");
}
I've tackled this before, can came to an elegant solution.
First, you'd want to setup your main classes to send, as well as a 'holder' class to store them to eventually send to a view.
As you probably found out, this is because a view can't have multiple models sent to it.
public class WebsiteTheme
{
public string Color { get;set; }
public string Title { get;set; }
public WebsiteTheme() {
Color = "blue";
Title = "test website";
}
}
public class User
{
public string Name { get;set; }
public string Gender { get;set; }
public User() {
Name = "Anonymous";
Gender = "Unspecified";
}
}
public class ToPage
{
public WebsiteTheme WebsiteTheme{ get; set; }
public User User { get; set; }
public ToPage() {
websiteTheme = new WebsiteTheme();
user = new User();
}
}
This will allow you to send any amount of classes to your page.
Then, in your controller, you'd want to populate those classes. Make sure to initialise them all first, then set the populated classes to your holder class.
WebsiteTheme websiteTheme = new WebsiteTheme();
websiteTheme.Color = "orange";
User user = new User();
user.Name = "Darren";
ToPage toPage = new ToPage();
toPage.User = user;
toPage.WebsiteTheme = websiteTheme;
return View(toPage);
In your view, you'd call them in any way you want to. But make sure to use HolderModel.SpecifiedModel in every case.
#model WebApplication1.Models.ToPage
#Html.DisplayFor(model => model.User.Name)
I did a compound model like this:
public class CompoundModel
{
public SearchModel SearchModel { get; set; }
public QueryResultRow ResultModel { get; set; }
}
public class QueryResultRow
{
[DisplayName("Id")]
public long id { get; set; }
[DisplayName("Importdatum")]
public System.DateTime importdate { get; set; }
[DisplayName("Mandant")]
public int indexBMClient { get; set; }
}
public class SearchModel
{
[Required]
[DataType(DataType.Date)]
[Display(Name = "Zeitraum von")]
public DateTime dateFrom { get; set; }
[Display(Name = "Terminal-ID")]
public string tid { get; set; }
[Display(Name = "Belegnummer")]
public string receiptnumber { get; set; }
}
In the view header:
#model MyProject_aspmvc.Models.CompoundModel
And get data access from the SearchModel, for example:
model => model.SearchModel.tid
and data access from the ResultModel, for example:
model => model.ResultModel.importdate