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.
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 action that takes a complex object as an input. I want to be able to populate any of the values with either POST data, or from the query string in a GET request. This works fine.
I also want to provide a default value if no user input was provided, however, this is not working because filter is never null, even if there were no querystring params from a GET request. What happens instead is, MVC just calls the model's default constructor without setting any properties instead of giving me a null.
public ActionResult Index(DataFilterInput filter = null)
{
if (filter == null)
filter = new DataFilterInput { Top = 100 };
var model = new IndexModel();
return View(model);
}
How can I know whether I should be defaulting the values in the absence of user input (I do not want to go into the Request query string or form collections)?
to provide a default value for your object this sample would work
public class SearchModel
{
public bool IsMarried{ get; set; }
public SearchModel()
{
IsMarried= true;
}
}
and if you want to validate the model
public ActionResult Index(DataFilterInput filter = null)
{
if (!ModelState.Isvalied)
filter = new DataFilterInput { Top = 100 };
var model = new IndexModel();
return View(model);
}
You can decare top nullable
public int? Top {get;set;}
So when no top value is provided it will be null by default and you can check it by using ==null or HasValue like this
public ActionResult Index(DataFilterInput filter )
{
if (!filter.Top.HasValue )
filter = new DataFilterInput { Top = 100 };
var model = new IndexModel();
return View(model);
}
MVC noob here and cannot find a simple enough explanation from this.
I just started working on a fairly large app built with MVC.
In the controller I'm using, most of the ActionResults have the [HttpGet] attribute appended to them. So I'm building off that code, and I built two ActionResults myself but left the [HttpGet] attributes off.
These make calls out to the database layer and then return results to the view. They work fine. When I noticed they didn't have [HttpGet] on them, I added them and then the calls stopped working. I can't figure out why, or the rhyme and reason for when they have to be there.
Here's a call I'm making from the view:
function getExcelExport() {
var activePane = $('div.tab-pane.active');
var agencyCompany = $(activePane).find('#Agency_AgencyId').val();
if (!$(activePane).find('#form0').valid()) { return false; }
var month = $(activePane).find('#CommissionMonth').val();
var year = $(activePane).find('#CommissionYear').val();
window.location = 'AgencyManagement/GetCommissionsExcel?agencyID=' + agencyCompany + '&month=' + month + '&year=' + year;
};
and here's the action in the controller:
public ActionResult GetCommissionsExcel(string agencyid, string month, string year)
{
try
{
var as400rep = new iSeriesRepository(new iSeriesContext());
var results = as400rep.GetCommissionExcel(agencyid, month, year);
string xml = String.Empty;
XmlDocument xmlDoc = new XmlDocument();
XmlSerializer xmlSerializer = new XmlSerializer(results.GetType());
using (System.IO.MemoryStream xmlStream = new System.IO.MemoryStream())
{
xmlSerializer.Serialize(xmlStream, results);
xmlStream.Position = 0;
xmlDoc.Load(xmlStream);
xml = xmlDoc.InnerXml;
}
var fName = string.Format("CommissionsExcelExport-{0}", DateTime.Now.ToString("s"));
fName = fName + ".xls";
byte[] fileContents = System.Text.Encoding.UTF8.GetBytes(xml);
return File(fileContents, "application/vnd.ms-excel", fName);
}
catch (Exception ex)
{
Log.Error(ex.Message, ex.InnerException);
throw;
}
}
Is there a simple explanation for this?
The reason that when you added [HttpGet] and the calls 'stopped' working is because you would be calling the method using a different HTTP verb e.g. POST.
Applying a Http verb attribute on a method means, to restrict an action method so that the method handles only HTTP GET requests.
The reason why it all worked when you did not use a http verb attribute is because that action method is then available via all Http verbs.
Mark that Action Method with [HttpPost] and it will work.
[HttpPost]
public ActionResult Action(int id)
{
}
You are able to have the same Method name for a GET and a POST, but the method requires a different signature (overloading).
[HttpGet]
public ActionResult Action() { }
[HttpPost]
public ActionResult Action(int id) { }
This is usually used in a PRG pattern (POST, Redirect, GET). You can read further about this here
[HttpGet] marks the Action as application only for GET requests -
Consider the following:
public ActionResult DoSomething() { }
if you were to GET to /DoSomething OR POST /DoSomething - the action would be invoked.
Specifying:
[HttpGet]
public ActionResult DoSomething() { }
ensures this is only going to be called if the request was a GET
I have a UI that looks like this:
I am trying to data of the newly created row to the server so that the server may save it.
I am sending data in JSON format from the client to my MVC application. Here's my ajax request:
var values = []; // an array with each item being an object/associative array
// more code to get values into variables
for (var i = 0; i < cultures.length; i++) {
var cultureName = cultures[i];
var valueTextBox = $(row).find(...);
var value = $(valueTextBox).val();
var cultureNameAndValue = { 'CultureShortName' : cultureName, 'StringValue' : value };
values.push(cultureNameAndValue);
}
var stringTableRow =
{
'ResourceKeyId': resourceKeyId,
'Key': resourceKeyName,
'CategoryId': categoryId,
'CategoryName': categoryName,
'StringValues': values
};
var stringified = JSON.stringify({ StringTableRow: stringTableRow });
$.ajax('/Strings/JsonCreateNew',
{
cache: false,
async: false,
type: 'POST',
contentType: 'application/json; charset=UTF-8',
data: stringified,
dataType: 'json',
error: SaveNewResourceClientSideHandler.OnError,
success: SaveNewResourceClientSideHandler.OnSuccess
});
Here's the data it sends (as seen in Firebug):
{"StringTableRow":{"ResourceKeyId":"","Key":"Foo",
"CategoryId":"1","CategoryName":"JavaScript",
"StringValues":[
{"CultureShortName":"en-US","StringValue":"Something"},
{"CultureShortName":"fr-FR","StringValue":""}]
}}
Here's my server side code:
public ActionResult JsonCreateNew(StringTableRow row)
{
// CreateNewStringTableRow(row);
// return a success code, new resource key id, new category id
// return Json(
new { Success = true, ResourceKeyId = row.ResourceKeyId,
CategoryId = row.CategoryId },
JsonRequestBehavior.AllowGet);
return new EmptyResult();
}
And here's the business object that I want my incoming POST'ed data to be bound to:
public class StringTableRow
{
public StringTableRow()
{
StringValues = new List<CultureNameAndStringValue>();
}
public long ResourceKeyId { get; set; }
public string Key { get; set; }
public long CategoryId { get; set; }
public string CategoryName { get; set; }
public IList<CultureNameAndStringValue> StringValues { get; set; }
}
public class CultureNameAndStringValue
{
public string CultureShortName { get; set; }
public string StringValue { get; set; }
}
Global.asax
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
ValueProviderFactories.Factories.Add(new JsonValueProviderFactory());
}
}
Problem:
The action JsonCreateNew receives an object that is not null but has all properties uninitialized, i.e all nullable properties are null and value properties are at their default values. Therefore, effectively I get no data at all even when the client sends a perfectly valid Json string.
Do I need to do custom model binding?
Okay, I solved my problem and this might be an important insight for other programmers as well, because I learnt something while solving my own problem.
My solution:
I resorted to not using JSON and instead used the default encoding that HTTP uses for encoding form posted values, and then I used something that smacks of custom model binding (without actually creating a model binder).
Explanation:
Normally, when you make an ajax request and pass data from the client to any server platform, if you do not specify the encoding, i.e. the contentType parameter in the settings object of jQuery's ajax method (or if you are using any other means to make the ajax request other than jQuery, then however that thing sets the ContentType HTTP header is what you're after), HTTP encodes your posted data using its default encoding, which is much like what you post with a GET request after the query string, only it is binary encoded and not sent as a part of the URL.
If that's the case, and you're posting a collection of any type (IList, IEnumerable, ICollection, IDictionary, etc.) to ASP.NET MVC, then don't create an associative array in JavaScript to embody the collection. Don't even create an array. Don't create anything.
In fact, just pass it along inside the main big object using the convention:
data =
{ /*scalar property */ Gar: 'har',
/* collection */ 'Foo[index++].Bar' : 'value1',
'Foo[index++].Bar' : 'value2'
}
And don't use JSON. That will solve half your problem.
On the server side, receive the posted data in a FormCollection and use its IValueProvider methods (GetValue) to dig out the data you need. You don't have to explicitly create a model binder to do it. You can do it in a private method of your controller itself.
I will improve this answer a bit later when I can find more time.
Use Backbone or KnockoutJs for data binding.
I am working on a project in asp.net mvc3(c#).
I need a solution for convert a view(not a partial view) to string from different controllers.
Code Explantion:
1) Calling "details" action of proposalmoduleController from proposalsController.
2) proposalmoduleController action "details" returns a view and convert this view(return result) as a string in proposalsController.
Code
public class proposalmoduleController : ControllerBase
{
[HttpGet]
public ActionResult details(int id, int widgetuniqueid)
{
//id - widgetid of div container
List<ModuleViewModel> listmoduleviewmodel = new List<ModuleViewModel>();
List<ModuleFieldViewModel> listmodulefieldviewmodel = new List<ModuleFieldViewModel>();
var objProposalModuleService = new ProposalModuleService();
var objModuleViewModel = new ModuleViewModel();
string WidgetTitle = "";
Int64 ModuleTemplateID = 0;
//objModuleViewModel.ProposalID = proposalid;
objModuleViewModel.ProposalModuleWidgetID = id;
listmoduleviewmodel=objProposalModuleService.Select(1, objModuleViewModel,out listmodulefieldviewmodel, out WidgetTitle, out ModuleTemplateID);
return View(listmoduleviewmodel);
}
}
public class proposalsController : ControllerBase
{
public string SaveHtml(int ProposalID)
{
var objProposalSortOrderViewModelList = new List<ProposalSortOrderViewModel>();
proposalmoduleController objModuleController = new proposalmoduleController(); // Initilize the object of proposalmoduleController for accessing details method
objProposalSortOrderViewModelList = GetProposalSortorders(ProposalID);
string result;
foreach (var item in objProposalSortOrderViewModelList)
{
ViewResult viewResult = (ViewResult)objModuleController.details(Convert.ToInt32(item.KeyID), Convert.ToInt32(item.SortOrder)); // Fetch the result returned from proposalmodulecontroller,details action
result=viewResult.ToString(); // Need to get result fetch from the proposalmodulecontroller,details action as a string
}
}
}
enter code here
Please suggest any solution.
A ViewResult is not a View. ViewResult is used by the MVC engine to determine the view that must be rendered.
I think it's better if you change your perspective:
if you want to include a partial view in a view just work on the presentation code using #Html.Partial
if you want to get the details data in your proposalsController don't call the action of the proposalmoduleController but call a service method that gives you the data