Output a variable name before the Json result - c#

I'm outputting the localization key/value pairs present in the JS localization resource into lang.js like this:
[Route("js/lang.js")]
public ActionResult Lang()
{
ResourceManager manager = new ResourceManager("Normandy.App_GlobalResources.JsLocalization", System.Reflection.Assembly.GetExecutingAssembly());
ResourceSet resources = manager.GetResourceSet(CultureInfo.CurrentCulture, true, true);
Dictionary<string, string> result = new Dictionary<string, string>();
IDictionaryEnumerator enumerator = resources.GetEnumerator();
while (enumerator.MoveNext())
result.Add((string)enumerator.Key, (string)enumerator.Value);
return Json(result);
}
The contents of /js/lang.js are (I include the file with a normal <script> tag):
{"Test":"test","_Lang":"en"}
Is there any way to make them be:
var LANG = {"Test":"test","_Lang":"en"}

You could use JSONP. Write a custom action result:
public class JsonpResult: ActionResult
{
public readonly object _model;
public readonly string _callback;
public JsonpResult(object model, string callback)
{
_model = model;
_callback = callback;
}
public override void ExecuteResult(ControllerContext context)
{
var js = new JavaScriptSerializer();
var jsonp = string.Format(
"{0}({1})",
_callback,
js.Serialize(_model)
);
var response = context.HttpContext.Response;
response.ContentType = "application/json";
response.Write(jsonp);
}
}
and then in your controller action return it:
[Route("js/lang.js")]
public ActionResult Lang()
{
...
return new JsonpResult(result, "cb");
}
and finally define the callback to capture the json before including the script:
<script type="text/javascript">
function cb(json) {
// the json argument will represent the JSON data
// {"Test":"test","_Lang":"en"}
// so here you could assign it to a global variable
// or do something else with it
}
</script>
<script type="text/javascript" src="js/lang.js"></script>

It looks like you want to return a script, instead of a json object. in that case I would do one of 2 things
return an action result that encapsulates a script rather than json (not sure if one exists)
return json and then set that json to a local variable on the client (typical ajax call)

Related

MVC - How to deserialize a Dictionary without having brackets [0] added to the key?

my deserialize Dictionary's key results in "brand[0]" when I send in "brand" to the api.
I have a class like this:
public class SearchRequest
{
public bool Html { get; set; } = false;
public Dictionary<string, HashSet<string>> Tags { get; set; }
}
// MVC Controller
[HttpPost]
public ActionResult Index(SearchRequest searchRequest)
{
...
}
And a json request like this that I post to the controller:
{
"html": true,
"tags": {
"brand": [
"bareminerals"
]
}
}
The binding seams to work and the searchRequest object is created but the resulting dictionary dose not have the key "brand" in it but insted the key "brand[0]" how can I preserve the real values I send in?
Edit: I need tags to be able to contain multiple tags, with multiple options, this was a simpel example.
One soulution to my problem is to create a custom model bind, so this is what am using now, but I dont understand why I need to, and I feel like there should be a easyer way? But am gonna leve It here anyhow.
public class FromJsonBodyAttribute : CustomModelBinderAttribute
{
public override IModelBinder GetBinder()
{
return new JsonModelBinder();
}
private class JsonModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var stream = controllerContext.HttpContext.Request.InputStream;
stream.Position = 0;
using (var reader = new StreamReader(stream))
{
var checkoutOrderDataStr = reader.ReadToEnd();
return JsonConvert.DeserializeObject(checkoutOrderDataStr, bindingContext.ModelType);
}
}
}
}
I'm not sure what is going on with your setup. You should not need a custom binder. I still think the problem is most likely with your calling code - whatever you're using as a client.
I'm using Asp.net Core 3.1. Here's what I threw together as a quick test.
Created Asp.net Core web application template with MVC. I declared two classes - a request POCO and a result POCO. The request was your class:
public class SearchRequest
{
public bool Html { get; set; } = false;
public Dictionary<string, HashSet<string>> Tags { get; set; }
}
The result was the same thing with a datetime field added just for the heck of it:
public class SearchResult : SearchRequest
{
public SearchResult(SearchRequest r)
{
this.Html = r.Html;
this.Tags = r.Tags;
}
public DateTime RequestedAt { get; set; } = DateTime.Now;
}
I Added a simple post method on the default HomeController.
[HttpPost]
public IActionResult Index([FromBody] SearchRequest searchRequest)
{
return new ObjectResult(new SearchResult(searchRequest));
}
I added a console Application to the solution to act as a client. I copied the two class definitions into that project.
I added this as the main method. Note you can either have the camel casing options on the request or not - asp.net accepted either.
static async Task Main(string[] _)
{
var tags = new[] { new { k = "brand", tags = new string[] { "bareminerals" } } }
.ToDictionary(x => x.k, v => new HashSet<string>(v.tags));
var request = new SearchRequest() { Html = true, Tags = tags };
var options = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase };
var json = JsonSerializer.Serialize(request, options);
Console.WriteLine(json);
using (var client = new HttpClient())
{
var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await client.PostAsync("http://localhost:59276", content);
response.EnsureSuccessStatusCode();
var data = await response.Content.ReadAsStringAsync();
var result = JsonSerializer.Deserialize<SearchResult>(data, options);
Console.WriteLine(data);
var keysSame = Enumerable.SequenceEqual(request.Tags.Keys, result.Tags.Keys);
var valuesSame = Enumerable.SequenceEqual(request.Tags.Values.SelectMany(x => x),
result.Tags.Values.SelectMany(x=>x));
Console.WriteLine($"Keys: {keysSame} Values: {valuesSame}");
}
}
This outputs:
{"html":true,"tags":{"brand":["bareminerals"]}}
{"requestedAt":"2020-10-30T19:22:17.8525982-04:00","html":true,"tags":{"brand":["bareminerals"]}}
Keys: True Values: True

How can i get Javascript Callback in .Net Blazor?

Is there a way to add callback to Javascript and get the result in Blazor? Apart from JS Promises.
for example, let say i want to load a file
Javascript Code
window.readFile = function(filePath, callBack) {
var reader = new FileReader();
reader.onload = function (evt) {
callBack(evt.target.result);
};
reader.readAsText(filePath);
}
Can i have something like this in Blazor C#
// read file content and output result to console
void GetFileContent() {
JsRuntime.InvokeAsync<object>("readFile", "file.txt", (string text) => {
Console.Write(text);
});
}
Or Maybe something like this
// read with javascript
void ReadFileContent() {
JsRuntime.InvokeAsync<object>("readFile", "file.txt", "resultCallbackMethod");
}
// output result callback to console
void resultCallbackMethod(string text) {
Console.Write(text);
}
Thanks
UPDATE 1:
After re-reading your question, I think this would cover your 2nd example
I think you have the option of implementing a JS proxy function that handle the calling. Something like this:
UPDATE 2:
Code was updated with a functional (but not deeply tested) version, you can also find a working example in blazorfiddle.com
JAVASCRIPT CODE
// Target Javascript function
window.readFile = function (filePath, callBack) {
var fileInput = document.getElementById('fileInput');
var file = fileInput.files[0];
var reader = new FileReader();
reader.onload = function (evt) {
callBack(evt.target.result);
};
reader.readAsText(file);
}
// Proxy function
// blazorInstance: A reference to the actual C# class instance, required to invoke C# methods inside it
// blazorCallbackName: parameter that will get the name of the C# method used as callback
window.readFileProxy = (instance, callbackMethod, fileName) => {
// Execute function that will do the actual job
window.readFile(fileName, result => {
// Invoke the C# callback method passing the result as parameter
instance.invokeMethodAsync(callbackMethod, result);
});
}
C# CODE
#page "/"
#inject IJSRuntime jsRuntime
<div>
Select a text file:
<input type="file" id="fileInput" #onchange="#ReadFileContent" />
</div>
<pre>
#fileContent
</pre>
Welcome to your new app.
#code{
private string fileContent { get; set; }
public static object CreateDotNetObjectRefSyncObj = new object();
public async Task ReadFileContent(UIChangeEventArgs ea)
{
// Fire & Forget: ConfigureAwait(false) is telling "I'm not expecting this call to return a thing"
await jsRuntime.InvokeAsync<object>("readFileProxy", CreateDotNetObjectRef(this), "ReadFileCallback", ea.Value.ToString()).ConfigureAwait(false);
}
[JSInvokable] // This is required in order to JS be able to execute it
public void ReadFileCallback(string response)
{
fileContent = response?.ToString();
StateHasChanged();
}
// Hack to fix https://github.com/aspnet/AspNetCore/issues/11159
protected DotNetObjectRef<T> CreateDotNetObjectRef<T>(T value) where T : class
{
lock (CreateDotNetObjectRefSyncObj)
{
JSRuntime.SetCurrentJSRuntime(jsRuntime);
return DotNetObjectRef.Create(value);
}
}
}
I believe that you are looking for the info on the documentation here:
https://learn.microsoft.com/en-us/aspnet/core/blazor/javascript-interop?view=aspnetcore-3.0#invoke-net-methods-from-javascript-functions
It shows how to call the Razor.Net from Javascript.
The documentation has more information, but essentially you will need the [JSInvokable] attribute on the method in razor, and calling via DotNet.invokeMethod in javascript.
Using the tips on this page I came up with a more generic version that works with nearly any callback based function.
Update:
You can now call any function whose last argument is a callback. You can pass any number of arguments to the function and the callback can have any number of arguments returned.
The function InvokeJS returns an instance of CallbackerResponse which can be used to get the typed value of any of the response arguments. See examples and code for more information.
Based on OP callback (fileContents (string)):
Example 1 (C# Blazor with await):
var response = await Callbacker.InvokeJS("window.readFile", filename);
var fileContents = response.GetArg<string>(0);
// fileContents available here
Example 2 (C# Blazor with callback):
Callbacker.InvokeJS((response) => {
var fileContents = response.GetArg<string>(0);
// fileContents available here
}, "window.readFile", filename);
Based on common callback (error (string), data (object)):
Example 3 (C# Blazor with await):
// To call a javascript function with the arguments (arg1, arg2, arg3, callback)
// and where the callback arguments are (err, data)
var response = await Callbacker.InvokeJS("window.myObject.myFunction", arg1, arg2, arg3);
// deserialize callback argument 0 into C# string
var err = response.GetArg<string>(0);
// deserialize callback argument 1 into C# object
var data = response.GetArg<MyObjectType>(1);
In your Blazor Program.cs Main add singleton (or scoped if desired) Callbacker
builder.Services.AddSingleton<Services.Callbacker>();
Add Callbacker service in your Blazor page. Example: MyPage.razor.cs
[Inject]
public Callbacker Callbacker { get; set; }
C#
using Microsoft.JSInterop;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Home.Services
{
public class CallbackerResponse
{
public string[] arguments { get; private set; }
public CallbackerResponse(string[] arguments)
{
this.arguments = arguments;
}
public T GetArg<T>(int i)
{
return JsonConvert.DeserializeObject<T>(arguments[i]);
}
}
public class Callbacker
{
private IJSRuntime _js = null;
private DotNetObjectReference<Callbacker> _this = null;
private Dictionary<string, Action<string[]>> _callbacks = new Dictionary<string, Action<string[]>>();
public Callbacker(IJSRuntime JSRuntime)
{
_js = JSRuntime;
_this = DotNetObjectReference.Create(this);
}
[JSInvokable]
public void _Callback(string callbackId, string[] arguments)
{
if (_callbacks.TryGetValue(callbackId, out Action<string[]> callback))
{
_callbacks.Remove(callbackId);
callback(arguments);
}
}
public Task<CallbackerResponse> InvokeJS(string cmd, params object[] args)
{
var t = new TaskCompletionSource<CallbackerResponse>();
_InvokeJS((string[] arguments) => {
t.TrySetResult(new CallbackerResponse(arguments));
}, cmd, args);
return t.Task;
}
public void InvokeJS(Action<CallbackerResponse> callback, string cmd, params object[] args)
{
_InvokeJS((string[] arguments) => {
callback(new CallbackerResponse(arguments));
}, cmd, args);
}
private void _InvokeJS(Action<string[]> callback, string cmd, object[] args)
{
string callbackId;
do
{
callbackId = Guid.NewGuid().ToString();
} while (_callbacks.ContainsKey(callbackId));
_callbacks[callbackId] = callback;
_js.InvokeVoidAsync("window._callbacker", _this, "_Callback", callbackId, cmd, JsonConvert.SerializeObject(args));
}
}
}
JS
window._callbacker = function(callbackObjectInstance, callbackMethod, callbackId, cmd, args){
var parts = cmd.split('.');
var targetFunc = window;
var parentObject = window;
for(var i = 0; i < parts.length; i++){
if (i == 0 && part == 'window') continue;
var part = parts[i];
parentObject = targetFunc;
targetFunc = targetFunc[part];
}
args = JSON.parse(args);
args.push(function(e, d){
var args = [];
for(var i in arguments) args.push(JSON.stringify(arguments[i]));
callbackObjectInstance.invokeMethodAsync(callbackMethod, callbackId, args);
});
targetFunc.apply(parentObject, args);
};
Thanks for that #Henry Rodriguez. I created something out of it, and i believed it might be helpful as well.
Note that DotNetObjectRef.Create(this) still works fine in other method. It is only noted to have problem with Blazor lifecycle events in preview6. https://github.com/aspnet/AspNetCore/issues/11159.
This is my new Implementation.
<div>
Load the file content
<button #click="#ReadFileContent">Get File Content</button>
</div>
<pre>
#fileContent
</pre>
Welcome to your new app.
#code{
string fileContent;
//The button onclick will call this.
void GetFileContent() {
JsRuntime.InvokeAsync<object>("callbackProxy", DotNetObjectRef.Create(this), "readFile", "file.txt", "ReadFileCallback");
}
//and this is the ReadFileCallback
[JSInvokable] // This is required for callable function in JS
public void ReadFileCallback(string filedata) {
fileContent = filedata;
StateHasChanged();
}
And in blazor _Host.cshtml or index.html, include the callback proxy connector
// Proxy function that serves as middlemen
window.callbackProxy = function(dotNetInstance, callMethod, param, callbackMethod){
// Execute function that will do the actual job
window[callMethod](param, function(result){
// Invoke the C# callback method passing the result as parameter
return dotNetInstance.invokeMethodAsync(callbackMethod, result);
});
return true;
};
// Then The Javascript function too
window.readFile = function(filePath, callBack) {
var reader = new FileReader();
reader.onload = function (evt) {
callBack(evt.target.result);
};
reader.readAsText(filePath);
}
This works perfectly for what i needed, and it is re-usable.

Swift 4 Alamofire upload file with parameters to ASP.NET MVC Controller

I have an ASP.NET MVC Controller defined like so:
[HttpPost]
public string uploadQAImage(FileUploadClass fileUploadClass, HttpPostedFile image)
{
}
And this what the class FileUploadClass looks like.
public class FileUploadClass
{
public string job { get; set; }
public string createdBy { get; set; }
public string itemId { get; set; }
}
What I am trying to do with Alamofire in iOS is call this Controller, I have tried using parameters:
func saveQAPhotos(_ cellHolder: PhotoClass, completion: #escaping (_ result: Dictionary<String, Any>) -> Void)
{
let parameters: Parameters = [
"job" : cellHolder.job!,
"itemId" : cellHolder.itemId!,
"createdBy" : appDelegate.username!
]
let urlComponents = NSURLComponents(string: webservice + "uploadQAImage");
urlComponents?.user = appDelegate.username;
urlComponents?.password = appDelegate.password;
let url = urlComponents?.url;
Alamofire.upload(multipartFormData: { (multipartFormData) in
for (key, value) in parameters {
multipartFormData.append("\(value)".data(using: String.Encoding.utf8)!, withName: key as String)
}
if let data = cellHolder.photo {
multipartFormData.append(data, withName: "image", fileName: "image.png", mimeType: "image/png")
}
}, usingThreshold: UInt64.init(), to: url!, method: .post, headers: nil) { (result) in
switch result{
case .success(let upload, _, _):
upload.responseJSON { response in
if let result = response.result.value {
let jsonData = result as! Dictionary<String, Any>
completion(jsonData)
}
}
case .failure(_):
print("error")
}
}
}
But that didn't work on the ASP.NET side I get this error:
can't bind multiple parameters to the request's content
I have also tried sending the data as [AnyHashable: Any] like so:
func saveQAPhotos(_ cellHolder: PhotoClass, completion: #escaping (_ result: Dictionary<String, Any>) -> Void)
{
var jsonDict = [AnyHashable: Any]()
jsonDict["job"] = cellHolder.job
jsonDict["itemId"] = cellHolder.itemId
jsonDict["createdBy"] = appDelegate.username
let jsonData: Data? = try? JSONSerialization.data(withJSONObject: jsonDict, options: .prettyPrinted)
let urlComponents = NSURLComponents(string: webservice + "uploadQAImage");
urlComponents?.user = appDelegate.username;
urlComponents?.password = appDelegate.password;
let url = urlComponents?.url;
Alamofire.upload(multipartFormData: { (multipartFormData) in
multipartFormData.append(jsonData!, withName: "fileUploadClass", mimeType: "application/json")
if let data = cellHolder.photo {
multipartFormData.append(data, withName: "image", fileName: "image.png", mimeType: "image/png")
}
}, usingThreshold: UInt64.init(), to: url!, method: .post, headers: nil) { (result) in
switch result{
case .success(let upload, _, _):
upload.responseJSON { response in
if let result = response.result.value {
let jsonData = result as! Dictionary<String, Any>
completion(jsonData)
}
}
case .failure(_):
print("error")
}
}
}
Same error as before
can't bind multiple parameters to the request's content
Now when I change the ASP.NET Controller to only get FileUploadClass like so:
[HttpPost]
public string uploadQAImage(FileUploadClass fileUploadClass)
{
}
I get this error:
Object reference not set to an instance of an object.
I can only assume if I do fix the can't bind multiple parameters to the request's content error I will get this error next.
I am pretty sure I am sending the data incorrectly, so I guess my question is how do I send data from Alamofire upload method to an ASP.NET MVC method?

How can I pass multiple Json objects to ASP.net MVC controller?

I would really appreciate a tip here... I've been looking for a solution for 4 hours now...
I have a function like so:
public virtual JsonResult LoadPreviousProductsJson(SearchResultModel rmodel, SearchCriteriaModel cmodel)
I'm trying to send data to this controller like so:
var jsonData = $('#frmSearchResult').serialize();
var stringToPost = JSON.stringify(jsonData);
var jsonData2 = $('#frmSearchProducts').serialize();
var stringToPost2 = JSON.stringify(jsonData2);
$.post('#Url.Action(MVC.Product.LoadPreviousProductsJson())', { rmodel: stringToPost, cmodel: stringToPost2 })
.done(function(data) {....
This results that the objects are Null in the controller...
If I only send 1 Json objectI am succesfull:
$.post('#Url.Action(MVC.Product.LoadPreviousProductsJson())', stringToPost)
.done(function(data) {....
but when I try to send them together, it Always fails...
Only somewhat successful thing I can do is sending the 2 objects as string and read them with Newtonsoft, but here I can't convert the strings to the corresponding objects....
model = Newtonsoft.Json.JsonConvert.DeserializeObject<SearchResultModel>(rmodel);
model2 = Newtonsoft.Json.JsonConvert.DeserializeObject<SearchCriteriaModel>(cmodel);
The above code just fails...
Create a new model to store your payload that is specific to your action
public class SearchViewModel {
public SearchResultModel rmodel { get; set; }
public SearchCriteriaModel cmodel { get; set; }
}
Update action to accept expected payload
public virtual JsonResult LoadPreviousProductsJson(SearchViewModel model) {
SearchResultModel rmodel = model.rmodel;
SearchCriteriaModel cmodel = model.cmodel;
//... other code
}
create the same mode on client and send one payload.
var jsonData = {};
$('#frmSearchResult').serializeArray()
.map(function(x){jsonData[x.name] = x.value;});
var jsonData2 = {};
$('#frmSearchProducts').serializeArray()
.map(function(x){jsonData2[x.name] = x.value;});
var model = { rmodel:jsonData, cmodel:jsonData2 };
var payload = JSON.stringify(model);
$.post('#Url.Action(MVC.Product.LoadPreviousProductsJson())', payload)
.done(function(data) {....}
public class SearchViewModel
{
public SearchResultModel SearchResultModel{ get; set; }
public SearchCriteriaModel SearchCriteriaModel{ get; set; }
}
public virtual JsonResult LoadPreviousProductsJson(string model)
{
var modelClass = JsonConvert.DeserializeObject<SearchViewModel>(model);
var searchResultModel = modelClass.SearchCriteriaModel;
var searchCriteriaModel = modelClass.SearchResultModel;
//... other code
}
var jsonData = $('#frmSearchResult').serialize();
var jsonData2 = $('#frmSearchProducts').serialize();
var model = { SearchResultModel: jsonData, SearchCriteriaModel:jsonData2 };
var url = "/Area/Controller/Action" + "?model=" + JSON.stringify(model) + "";
$.ajax({
url: url,
dataType: "JSON",
type: "GET",
success: function () {
}
});
Hope this might be helpful
First of all, thanks to inkosi and krish for giving tips (thumbs up for both). His answer wasn't exactly what I needed since I still was getting null values.
Here is what finally worked for me.
controller:
public virtual JsonResult LoadPreviousProductsJson(string rmodel, string cmodel){
SearchResultModel model = new SearchResultModel();
SearchCriteriaModel modelSearchCriteria = new SearchCriteriaModel();
model = Newtonsoft.Json.JsonConvert.DeserializeObject<SearchResultModel>(rmodel);
modelSearchCriteria = Newtonsoft.Json.JsonConvert.DeserializeObject<SearchCriteriaModel>(cmodel);
.......
}
Javascript:
$.fn.serializeObject = function () {
var o = {};
var a = this.serializeArray();
$.each(a, function () {
if (o[this.name]) {
if (!o[this.name].push) {
o[this.name] = [o[this.name]];
}
o[this.name].push(this.value || '');
} else {
o[this.name] = this.value || '';
}
});
var jsonDataNewResult = $('#frmSearchResult').serializeObject();
var stringjsonDataNewResult = JSON.stringify(jsonDataNewResult);
var jsonDataNewCriteria = $('#frmSearchProducts').serializeObject();
var stringjsonDataNewCriteria = JSON.stringify(jsonDataNewCriteria);
$.post('#Url.Action(MVC.Product.LoadPreviousProductsJson())',
{ rmodel: stringjsonDataNewResult, cmodel: stringjsonDataNewCriteria })
.done(function(data) {
What a day :-( , I must have tried a hundred things... I don't even remember all the things I tried..... happy to get here, now to explain to my boss why something this trivial has taken me so long.... I miss desktop programming !
PS. Thanks to these 2 SO posts:
Convert form data to JavaScript object with jQuery
Consume jQuery.serializeArray in ASP.NET MVC

How to retrieve JsonResult data

I have the following Action in my layouts Controller
public JsonResult getlayouts(int lid)
{
List<layouts> L = new List<layouts>();
L = db.LAYOUTS.Where(d => d.seating_plane_id == lid).ToList()
return new JsonResult { Data = L, JsonRequestBehavior = JsonRequestBehavior.AllowGet };
}
I am calling this Action from another controller like so:
layoutsController L = new layoutsController();
JsonResult result = L.getlayouts(lid);
My question is: how can I get the data from result object?
Well, have a look how you're building the object:
new JsonResult { Data = L, JsonRequestBehavior = JsonRequestBehavior.AllowGet }
You're setting the L variable to a property called Data. So just read that property:
List<layouts> L = (List<layouts>)result.Data;
There's nothing special about the fact that it's an MVC controller action.
You're simply calling a method which returns an object that was constructed in the method, and reading properties from that object. Just like any other C# code.
I have my class:
public class ResponseJson
{
public string message { get; set; }
public bool success { get; set; }
}
in my method SendEmail
private async Task<JsonResult> SendEmailAsync(ApplicationUser user, string returnUrl, string empleadoNombre, string pwdInic)
i will return my JsonResult
ResponseJson response = new ResponseJson();
response.success = true;
response.message = "OperaciĆ³n exitosa";
return new JsonResult( response);
to read the result returned from my SendEmail method
JsonResult emailSend = await SendEmailAsync(user, returnUrl, empleadoNombre, pwdInic);
ResponseJson response = new ResponseJson();
try
{
string json = JsonConvert.SerializeObject(emailSend.Value);
response = JsonConvert.DeserializeObject<ResponseJson>(json);
}
catch(Exception e)
{
}

Categories