I am trying to setup and consume an asp.net webapi rest application and consume it from another project.
I have made a simple helper to call the service like
public static string GetApiResponse(string apiMethod,Dictionary<string,string>queryString=null)
{
using (var client = new WebClient())
{
client.Headers.Add("ApiKey", ConfigurationManager.AppSettings["ApiKey"]);
//add any query string values into the client
if (queryString != null)
{
foreach (var query in queryString)
{
client.QueryString.Add(query.Key, query.Value);
}
}
try
{
string url = string.Format("{0}{1}", ConfigurationManager.AppSettings["ApiBaseUrl"],apiMethod);
return(client.DownloadString(url));
}
catch (Exception ex)
{
return ex.Message;
}
}
}
I am consuming it from my controller in a different project like
private IEnumerable<CustomerModel> CustomerDetails()
{
var json = ApiRestHelper.GetApiResponse("Customer/Get");
var data = JsonConvert.DeserializeObject<CustomerViewModel>(json, new JsonSerializerSettings
{
});
The returned data from the service is looking like
[{"CustomerId":"24a62bf8-7a4e-4837-859d-1f04dc983011","FirstName":"Joe","LastName":"Bloggs","StoreCustomerId":null}]
My CustomerViewModel is
public class CustomerViewModel
{
public IEnumerable<CustomerModel> Customers { get; set; }
}
I can see the data that is returned is an array and I am trying to convert it to the list. I get an error
Cannot deserialize JSON array (i.e. [1,2,3]) into type 'WebApplication.Models.ViewModels.CustomerViewModel'.
The deserialized type must be an array or implement a collection interface like IEnumerable, ICollection or IList.
What do I need to change to allow the deserialization into my view model?
I was trying to do it wrong.
var data = JsonConvert.DeserializeObject<List<CustomerModel>>(json, new JsonSerializerSettings
{
});
Does the trick
Related
I'm building WebAPI & WebApp, both of them using ASP.NET Core 2.1
My Web App is trying to send post request to the Web API using ViewModel that contains IFormFile and other properties. I know I have to use MultipartFormDataContent to post IFormFile, but I don't know how to implement it with my ViewModel because my ViewModel has List of other model.
I already try to google some solutions, but I only found solutions with simple ViewModel without List like these :
https://stackoverflow.com/a/41511354/7906006
https://stackoverflow.com/a/55424886/7906006.
Is there any solution like
var multiContent = new MultipartFormDataContent();
var viewModelHttpContent= new StreamContent(viewModel);
MultiContent.Add(viewModelHttpContent, "viewModel");
var response = await client.PostAsJsonAsync("/some/url", multiContent);
so i don't have to add my property to MultipartFormDataContent one by one and post it as json.
Here's my Web App ViewModel
public class CreateDataViewModel
{
public string PrimaryKeyNumber{ get; set; }
public List<Currency> ListOfCurrency { get; set; }
public IList<DataDetail> dataDetails { get; set; }
[DataType(DataType.Upload)]
public IFormFile Attachment { get; set; }
//And other properties like Boolean, Datetime?, string
}
Here's my Web App controller
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(CreateDataViewModel viewModel)
{
//How to implement MultipartFormDataContent with my ViewModel in here ?
//My code below returns Could not create an instance of type Microsoft.AspNetCore.Http.IHeaderDictionary. Type is an interface or abstract class and cannot be instantiated. Path 'Attachment.Headers.Content-Disposition', line 1, position 723.
//It works fine if I don't upload a file
HttpResponseMessage res = await _client.PostAsJsonAsync<CreateDataViewModel>("api/data/create", viewModel);
var result = res.Content.ReadAsStringAsync().Result;
if (res.IsSuccessStatusCode)
{
TempData["FlashMessageSuccess"] = "Data have been submitted";
return RedirectToAction("Index", "Home"); ;
}
//Code for error checking
}
Here's my Web API controller that catches the post response using CreateDataViewModel as parameter.
[HttpPost]
[Route("[action]")]
public async Task<IActionResult> Create(CreateDataViewModel viewModel)
{
//Code to validate then save the data
}
don't know how to implement it with my ViewModel because my ViewModel has List of other model
You can refer to following code snippet and implement a custom model binder to achieve your requirement.
var multipartContent = new MultipartFormDataContent();
multipartContent.Add(new StringContent(viewModel.PrimaryKeyNumber), "PrimaryKeyNumber");
multipartContent.Add(new StringContent(JsonConvert.SerializeObject(viewModel.ListOfCurrency)), "ListOfCurrency");
multipartContent.Add(new StringContent(JsonConvert.SerializeObject(viewModel.dataDetails)), "dataDetails");
multipartContent.Add(new StreamContent(viewModel.Attachment.OpenReadStream()), "Attachment", viewModel.Attachment.FileName);
var response = await client.PostAsync("url_here", multipartContent);
Implement a custom model binder to convert incoming request data
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null)
{
throw new ArgumentNullException(nameof(bindingContext));
}
// code logic here
// ...
// ...
// fetch the value of the argument by name
// and populate corresponding properties of your view model
var model = new CreateDataViewModel()
{
PrimaryKeyNumber = bindingContext.ValueProvider.GetValue("PrimaryKeyNumber").FirstOrDefault(),
ListOfCurrency = JsonConvert.DeserializeObject<List<Currency>>(bindingContext.ValueProvider.GetValue("ListOfCurrency").FirstOrDefault()),
dataDetails = JsonConvert.DeserializeObject<List<DataDetail>>(bindingContext.ValueProvider.GetValue("dataDetails").FirstOrDefault()),
Attachment = bindingContext.ActionContext.HttpContext.Request.Form.Files.FirstOrDefault()
};
bindingContext.Result = ModelBindingResult.Success(model);
return Task.CompletedTask;
}
Apply it on API action method
public async Task<IActionResult> Create([ModelBinder(BinderType = typeof(CustomModelBinder))]CreateDataViewModel viewModel)
Test Result
Below soltuion worked for me:-
var multiContent = new MultipartFormDataContent();
var viewModelHttpContent= new StreamContent(viewModel);
multiContent.Add(viewModelHttpContent, "viewModel");
multiContent.Add(new StreamContent(file.OpenReadStream()), "Attachment", file.FileName);
var request = new HttpRequestMessage(HttpMethod.Post, "/some/url") { Content = multiContent};
var response = await client.SendAsync(request);
At Api end:-
public async Task Upload([FromForm] CreateDataViewModel postRequest)
I have an Openlayer's map interface where I'm capturing the user's adding new points to the map. What I want is to take those location data points and save them do a database. So I have a working function on the .cshtml page that looks like this:
map.on('dblclick', function (evt) {
var coordinate = evt.coordinate;
var datapoints = new Array();
var features = source.getFeatures();
for (var i = 0; i < features.length; i++) {
var poi = features[i];
var datapt = new Object();
datapt.X = poi.values_.geometry.flatCoordinates[0];
datapt.Y = poi.values_.geometry.flatCoordinates[1];
datapoints.push(datapt);
}
var xhr = new XMLHttpRequest();
xhr.open("POST", "Draw_Features", true);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.send(JSON.stringify(datapoints));
});
This seems to work just fine and is sending back an encoded JSON of all the locations. In my Controller file, I have the following:
[HttpGet]
public IActionResult Draw_Features()
{
return View();
}
[HttpPost]
public IActionResult Draw_Features(string someValue)
{
//TODO
return View("Index");
}
[HttpPost]
public IActionResult AddPointsToDB(string someValue)
{
//TODO
return View("Index");
}
I have two problems:
1) I want to return the data to the "AddPointsToDB()" function but it is instead going to the "Draw_Features()" one instead. How do I specify in the xhr.send() the correct landing function?
2) I was expecting the function to receive the JSON via the 'string someValue' variable. But that variable comes in NULL. What is the correct way to access that JSON from within the function?
Thanks!
EDIT: Fixed the JSON convert code which was bombing. Still the same questions...
EDIT 2: Showing POST data from Chrome
For a working demo, follow steps below:
Define a new model to receive the data instead of string input. For passing datapoints to controller action, it will be serialize and de-serialize by MVC Built-in function.
public class Coordinate
{
public double X { get; set; }
public double Y { get; set; }
}
Change Action with [FromBody]IList<Coordinate> coordinates which will specify the modele binding to read the model from body.
public IActionResult AddPointsToDB([FromBody]IList<Coordinate> coordinates)
{
//TODO
return View("Index");
}
As already point out, make sure you set the xhr.open("POST", "AddPointsToDB", true); with the action name you want.
I can not save data in database beacuse model can not convert to entity,
I am getting data from model but Add function can not add intodatabase.
[HttpPost]
public ActionResult Create(Patient Patient)
{
_context.Patients.Add(Patient); "(Error here)"
try
{
_context.SaveChanges();
}
catch (DbEntityValidationException ex)
{
foreach (var entityValidationErrors in ex.EntityValidationErrors)
{
foreach (var validationError in entityValidationErrors.ValidationErrors)
{
Response.Write("Property: " + validationError.PropertyName + " Error: " + validationError.ErrorMessage);
}
}
}
return RedirectToAction("Index", "Patients");
}
}
Error :cannot convert from 'CandidateScreening.Models.Patient' to 'CandidateScreening.Data.Entities.Patient'
You have 2 different classes for viewmodel and data model, of course both of them cannot implicitly converted to each other. The simplest way to enable conversion is using implicit operator (or explicit operator, depending on context) to convert between viewmodel and data model, see this example below:
public static implicit operator Patient(PatientVM patient)
{
return new Patient
{
// list of properties
// example:
// PatientId = patient.PatientId
};
}
Then assign viewmodel contents to data model inside POST action method as provided below:
[HttpPost]
public ActionResult Create(PatientVM patient)
{
Patient patientData = patient;
_context.Patients.Add(patientData);
// other stuff
return RedirectToAction("Index", "Patients");
}
Note: The viewmodel class name intentionally changed in the example to differentiate between data model & viewmodel class.
You can also use Automapper. Here is an example:
CandidateScreening.Data.Entities.Patient patient = Mapper.Map<CandidateScreening.Data.Entities.Patient>(patientVm);//where patientVm has type CandidateScreening.Models.Patient
Can you have multiple custom model binders in Nancy? I need to bind the server side processing request from datatables jQuery plugin which doesn't fit with our current "mvc style" custom model binder. Specifically regarding lists, datatables presents them as mylist_0, mylist_1 etc instead of mylist [0], mylist [1].
So can I add another model binder to handle these differing list styles and if I do how does Nancy know which one to use?
You could add a custom ModelBinder to your project to handle the binding of the class you are talking about.
using System;
using System.IO;
using Nancy;
namespace WebApplication3
{
public class CustomModelBinder : Nancy.ModelBinding.IModelBinder
{
public object Bind(NancyContext context, Type modelType, object instance = null, params string[] blackList)
{
using (var sr = new StreamReader(context.Request.Body))
{
var json = sr.ReadToEnd();
// you now you have the raw json from the request body
// you can simply deserialize it below or do some custom deserialization
if (!json.Contains("mylist_"))
{
var myAwesomeListObject = new Nancy.Json.JavaScriptSerializer().Deserialize<MyAwesomeListObject>(json);
return myAwesomeListObject;
}
else
{
return DoSomeFunkyStuffAndReturnMyAwesomeListObject(json);
}
}
}
public MyAwesomeListObject DoSomeFunkyStuffAndReturnMyAwesomeListObject(string json)
{
// your implementation here or something
}
public bool CanBind(Type modelType)
{
return modelType == typeof(MyAwesomeListObject);
}
}
}
In case CustomModelBinder is not detected (as it happens to me), you can try overriding it in CustomBootstrapper:
protected override IEnumerable<Type> ModelBinders
{
get
{
return new[] { typeof(Binding.CustomModelBinder) };
}
}
I am trying to provide a simple RESTful API to my ASP MVC project. I will not have control of the clients of this API, they will be passing an XML via a POST method that will contain the information needed to perform some actions on the server side and provide back an XML with the result of the action. I don't have problems sending back XMLs, the problem is receiving XML via a POST. I have seen some JSON examples, but since I will not control my clients (it could be even a telnet from my point of view) I don't think JSON will work. Am I correct?
I have seen examples where clients simply construct the correct form format as part of the body of the request and then the ASP parse the message, and data is available as FormCollection (?param1=value1¶m2=value2&,etc). However, I want to pass pure XML as part of the message body.
thanks for your help,
#Freddy - liked your approach and improved on it with the following code to simplify stream reading:
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
HttpContextBase httpContext = filterContext.HttpContext;
if (!httpContext.IsPostNotification)
{
throw new InvalidOperationException("Only POST messages allowed on this resource");
}
Stream httpBodyStream = httpContext.Request.InputStream;
if (httpBodyStream.Length > int.MaxValue)
{
throw new ArgumentException("HTTP InputStream too large.");
}
StreamReader reader = new StreamReader(httpBodyStream, Encoding.UTF8);
string xmlBody = reader.ReadToEnd();
reader.Close();
filterContext.ActionParameters["message"] = xmlBody;
// Sends XML Data To Model so it could be available on the ActionResult
base.OnActionExecuting(filterContext);
}
Then in the Controller you can access the xml as a string:
[RestAPIAttribute]
public ActionResult MyActionResult(string message)
{
}
This could be accomplished by using the ActionFilterAttribute. Action Filters basically intersects the request before or after the Action Result. So I just built a custom action filter attribute for POST Action Result. Here is what I did:
public class RestAPIAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
HttpContextBase httpContext = filterContext.HttpContext;
if (!httpContext.IsPostNotification)
{
throw new InvalidOperationException("Only POST messages allowed on this resource");
}
Stream httpBodyStream = httpContext.Request.InputStream;
if (httpBodyStream.Length > int.MaxValue)
{
throw new ArgumentException("HTTP InputStream too large.");
}
int streamLength = Convert.ToInt32(httpBodyStream.Length);
byte[] byteArray = new byte[streamLength];
const int startAt = 0;
/*
* Copies the stream into a byte array
*/
httpBodyStream.Read(byteArray, startAt, streamLength);
/*
* Convert the byte array into a string
*/
StringBuilder sb = new StringBuilder();
for (int i = 0; i < streamLength; i++)
{
sb.Append(Convert.ToChar(byteArray[i]));
}
string xmlBody = sb.ToString();
//Sends XML Data To Model so it could be available on the ActionResult
base.OnActionExecuting(filterContext);
}
}
Then on the action result method on your controller you should do something like this:
[RestAPIAttribute]
public ActionResult MyActionResult()
{
//Gets XML Data From Model and do whatever you want to do with it
}
Hope this helps somebody else, if you think there are more elegant ways to do it, let me know.
Why can they not pass the xml as a string in the form post?
Example:
public ActionResult SendMeXml(string xml)
{
//Parse into a XDocument or something else if you want, and return whatever you want.
XDocument xmlDocument = XDocument.Parse(xml);
return View();
}
You could create a form post and send it in a single form field.
I know you can create a custom value provider factory. This will let you also validate your models when they are posted before attempting to save them. Phil Haack has a blog post about a JSON version of this same concept. The only problem is that I don't know how to implement one this same sort of thing for XML.
IMO the best way to accomplish this is to write a custom value provider, this is a factory that handles the mapping of the request to the forms dictionary. You just inherit from ValueProviderFactory and handle the request if it is of type “text/xml” or “application/xml.”
More Info:
Phil Haack
My blog
MSDN
protected override void OnApplicationStarted()
{
AreaRegistration.RegisterAllAreas();
RegisterRoutes(RouteTable.Routes);
ValueProviderFactories.Factories.Add(new JsonValueProviderFactory());
ValueProviderFactories.Factories.Add(new XmlValueProviderFactory());
}
XmlValueProviderFactory
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Web.Mvc;
using System.Xml;
using System.Xml.Linq;
public class XmlValueProviderFactory : ValueProviderFactory
{
public override IValueProvider GetValueProvider(ControllerContext controllerContext)
{
var deserializedXml = GetDeserializedXml(controllerContext);
if (deserializedXml == null) return null;
var backingStore = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
AddToBackingStore(backingStore, string.Empty, deserializedXml.Root);
return new DictionaryValueProvider<object>(backingStore, CultureInfo.CurrentCulture);
}
private static void AddToBackingStore(Dictionary<string, object> backingStore, string prefix, XElement xmlDoc)
{
// Check the keys to see if this is an array or an object
var uniqueElements = new List<String>();
var totalElments = 0;
foreach (XElement element in xmlDoc.Elements())
{
if (!uniqueElements.Contains(element.Name.LocalName))
uniqueElements.Add(element.Name.LocalName);
totalElments++;
}
var isArray = (uniqueElements.Count == 1 && totalElments > 1);
// Add the elements to the backing store
var elementCount = 0;
foreach (XElement element in xmlDoc.Elements())
{
if (element.HasElements)
{
if (isArray)
AddToBackingStore(backingStore, MakeArrayKey(prefix, elementCount), element);
else
AddToBackingStore(backingStore, MakePropertyKey(prefix, element.Name.LocalName), element);
}
else
{
backingStore.Add(MakePropertyKey(prefix, element.Name.LocalName), element.Value);
}
elementCount++;
}
}
private static string MakeArrayKey(string prefix, int index)
{
return prefix + "[" + index.ToString(CultureInfo.InvariantCulture) + "]";
}
private static string MakePropertyKey(string prefix, string propertyName)
{
if (!string.IsNullOrEmpty(prefix))
return prefix + "." + propertyName;
return propertyName;
}
private XDocument GetDeserializedXml(ControllerContext controllerContext)
{
var contentType = controllerContext.HttpContext.Request.ContentType;
if (!contentType.StartsWith("text/xml", StringComparison.OrdinalIgnoreCase) &&
!contentType.StartsWith("application/xml", StringComparison.OrdinalIgnoreCase))
return null;
XDocument xml;
try
{
var xmlReader = new XmlTextReader(controllerContext.HttpContext.Request.InputStream);
xml = XDocument.Load(xmlReader);
}
catch (Exception)
{
return null;
}
if (xml.FirstNode == null)//no xml.
return null;
return xml;
}
}
I like the answer from #Freddy and improvement from #Bowerm. It is concise and preserves the format of form-based actions.
But the IsPostNotification check will not work in production code. It does not check the HTTP verb as the error message seems to imply, and it is stripped out of HTTP context when compilation debug flag is set to false. This is explained here:
HttpContext.IsPostNotification is false when Compilation debug is false
I hope this saves someone a 1/2 day of debugging routes due to this problem. Here is the solution without that check:
public class XmlApiAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
HttpContextBase httpContext = filterContext.HttpContext;
// Note: for release code IsPostNotification stripped away, so don't check it!
// https://stackoverflow.com/questions/28877619/httpcontext-ispostnotification-is-false-when-compilation-debug-is-false
Stream httpBodyStream = httpContext.Request.InputStream;
if (httpBodyStream.Length > int.MaxValue)
{
throw new ArgumentException("HTTP InputStream too large.");
}
StreamReader reader = new StreamReader(httpBodyStream, Encoding.UTF8);
string xmlBody = reader.ReadToEnd();
reader.Close();
filterContext.ActionParameters["xmlDoc"] = xmlBody;
// Sends XML Data To Model so it could be available on the ActionResult
base.OnActionExecuting(filterContext);
}
}
...
public class MyXmlController
{ ...
[XmlApiAttribute]
public JsonResult PostXml(string xmlDoc)
{
...
Nice!,
What object I got in my controller method to manipulate the Xml?
I'm using this way:
On actionFilter, I populate the model with:
.
.
string xmlBody = sb.ToString();
filterContext.Controller.ViewData.Model = xmlBody;
And on my controller method, I get the Model as:
string xmlUserResult = ViewData.Model as string;
XmlSerializer ser = new XmlSerializer(typeof(UserDTO));
StringReader stringReader = new StringReader(xmlUserResult);
XmlTextReader xmlReader = new XmlTextReader(stringReader);
UserDTO userToUpdate = ser.Deserialize(xmlReader) as UserDTO;
xmlReader.Close();
stringReader.Close();
Is this a correct implementation?
Thanks.