I'm learning AWS Lambda with C#. My function looks sort of like this:
[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))]
namespace Function_Redeem
{
public class Function
{
public FunctionOutput FunctionHandler(FunctionInput input, ILambdaContext context)
{
// do work with input
// return FunctionOutput
}
public class FunctionInput
{
public string someData { get; set; }
}
public class FunctionOutput
{
public string someAnswer { get; set; }
}
}
}
It works fine when using the Test button in AWS, as well as the test feature in Visual Studio.
Now, I'm trying to call this from Unity.
So first, I added an API Gateway trigger, and left the defaults:
API endpoint: [the url]
API type: HTTP
Authorization: NONE
Cross-origin resource sharing (CORS): No
Enable detailed metrics: No
Method: ANY
Resource path: /FunctionName
Stage: default
Then in Unity,
private static IEnumerator TestFunction(string uri, string data)
{
UnityWebRequest webRequest = UnityWebRequest.Put(uri, data);
yield return webRequest.SendWebRequest();
if (webRequest.isNetworkError)
Debug.LogError("Network error: " + webRequest.error);
else
Debug.Log(webRequest.downloadHandler.text);
}
I call it, with data being
{"someData":"Hello"}
The function call works, I know that it is reaching my function, but the input data (i.e. the someData field) is null. It seems like it's not parsing the data I'm sending so FunctionInput defaults to null someData.
What am I missing?
Since you are using API Gateway as a trigger to your lambda function, accept APIGatewayProxyRequest as input parameter to your handler(instead of FunctionInput). The field Body would have your serialized payload {"someData":"Hello"}
public class Function
{
public FunctionOutput FunctionHandler(APIGatewayProxyRequest request, ILambdaContext context)
{ var requestBody = JsonConvert.DeserializeObject<FunctionInput>(request.Body);
// do work with input
// return FunctionOutput
}
public class FunctionInput
{
public string someData { get; set; }
}
public class FunctionOutput
{
public string someAnswer { get; set; }
}
}
}
Related
My working SignalR ASP.NET Core 5 Windows Service with a simple string payload (or even more value type parameters) does not work anymore when I change it to a (simple) complex object ("CommonMessage" in my case). From what I read, it should work out of the box. The "SendCommonMessage" method doesn't get called anymore and I am not getting any error. What am I missing? Is there no way of debugging/getting the error shown? (The ASP.NET Service gets called by a WPF Core 5 application.)
public class CommonMessageHub : Hub
{
public async Task SendCommonMessage(CommonMessage commonMessage)
{
await Clients.All.SendAsync("ReceiveCommonMessage", commonMessage);
}
}
public class CommonMessage
{
public int MessageType { get; set; }
public int Id { get; set; }
public string Text { get; set; }
}
The caller (WPF app) looks like so:
public class CommonMessageService
{
public CommonMessageService(HubConnection connection)
{
_connection = connection;
_connection.On<CommonMessage>("ReceiveCommonMessage", commonMessage => CommonMessageReceived?.Invoke(commonMessage));
}
private readonly HubConnection _connection;
public event Action<CommonMessage> CommonMessageReceived;
public async Task Connect()
{
await _connection.StartAsync();
}
public async Task SendCommonMessage(CommonMessage commonMessage)
{
await _connection.SendAsync("SendCommonMessage", commonMessage);
}
}
I am sending an object from my web project to my API project within the same solution. I have a class that uses RestSharp and acts as the sender to the API for all services.
Currently, there is one API controller that is receiving the object, but not all of the parameters are being retained and show up with null values via PUT. However, a different controller using the ApiClient's 'PutAsync' receives its own object with all the values intact.
I've even tried changing the method to receive as a POST, but still no success.
Am I missing something, or is there something wrong that is happening with the serialization/de-serialization of the object?
public class UserInfo
{
public int UserId { get; set; }
public string FirstName { get; private set; }
public string LastName { get; private set; }
public string EmailAddress { get; private set; }
}
internal class WebService : IWebService
{
public async Task<bool> UpdateProfile(UserInfo userInfo)
{
try
{
var url = "/User/UpdateProfile";
return await apiClient.PutAsync<UserInfo, bool>(url, userInfo);
}
catch (Exception ex)
{
this.logger.LogError("Error in UpdateProfile", ex);
throw;
}
}
}
RestSharp Setup
internal class ApiClient : IApiClient
{
public async Task<TOut> PutAsync<TIn, TOut>(string url, TIn data) where TIn : new()
{
return await PushData<TIn, TOut>(url, data, Method.PUT);
}
private async Task<TOut> PushData<TIn, TOut>(string url, TIn data, Method method) where TIn : new()
{
var request = new RestRequest(url, method);
request.AddHeader(HttpRequestHeader.Authorization.ToString(), $"Bearer {GetApiAccessToken()}");
request.AddJsonBody(data);
var result = await client.ExecuteAsync<TOut>(request);
if (result.StatusCode != HttpStatusCode.OK)
{
throw new Exception("Unable to push API data");
}
return result.Data;
}
}
Data in the request prior to being sent out, found under the Parameters, are as followed:
Data sent to API UserController
[Produces("application/json")]
[ApiController]
[Authorize]
[Route("api/[controller]")]
public class UserController : ControllerBase
{
[HttpPut]
[Route("UpdateProfile")]
public async Task<IActionResult> UpdateProfile([FromBody] ProfileUpdateInfo profileInfo)
{
try
{
var status = await this.service.UpdateProfile(profileInfo);
return Ok(status);
}
catch (Exception ex)
{
return BadRequest("Error in updating profile");
}
}
}
This is the Data that shows up in the parameter:
Data Consumed by API UserController
After reviewing my code again, the problem was that I had the set accessor to private on all string properties, which is why the int property was still coming through.
I used the HttpResponseMessage Post method to let the mobile terminal verify the account password. I used the following CODE to run successfully, but the POST format must be run like this.
'{"ID":"xxx","Password":"xxx"}'
It need two ' can run, I don't know why.
I can't request a service using the normal POST format on iOS or Android.
The format I want is {"ID":"xxx","Password":"xxx"},without '
[HttpPost]
public HttpResponseMessage Post([FromBody] string DATA)
{
using (appapidataEntities entities = new appapidataEntities())
{
//string controllerName = ControllerContext.RouteData.Values["controller"].ToString();
JObject jo = JObject.Parse(DATA);
string id = jo["ID"].ToString();
string password = jo["Password"].ToString();
var user = entities.USERs.Where(x => x.ID == id && x.Password == password).FirstOrDefault();
var result = new
{
message = "failure"
};
var result2 = new
{
message = "success"
};
if (user == null)
{
return Request.CreateResponse(HttpStatusCode.OK, result);
}
else
{
return Request.CreateResponse(HttpStatusCode.OK, result2);
}
}
}
public partial class USER
{
public string ID { get; set; }
public string Password { get; set; }
}
}
Please have someone with experience to help me, thank you very much.
As #Nkosi said, the correct way to receive a complex object is using a class/object (also called 'model binding')
In your models add this class. This class will be the 'contract' between the service with any external application who calls the service. Usually, any client (service, app or frontend) also has a class with this contract to call the service.
public class LoginViewModel {
public string ID { get; set; }
public string Password { get; set; }
}
Now, modify the controller as follow
[HttpPost]
public HttpResponseMessage Post([FromBody] LoginViewModel DATA) {
using (appapidataEntities entities = new appapidataEntities())
string id = DATA.ID;
string password = DATA.Password;
// rest of the code
}
}
Make sure the device is sending the data the service is waiting (maybe adding a breakpoint if you are debugging from Android Studio before to make the request) and add a breakpoint in your controller to verify that the variable DATA has the correct values.
Is there a way to programmatically enable/disable an Azure function?
I can enable/disable a function using the portal under the "Manage" section, which causes a request to be sent to https://<myfunctionapp>.scm.azurewebsites.net/api/functions/<myfunction>
The JSON payload looks a bit like:
{
"name":"SystemEventFunction",
"config":{
"disabled":true,
"bindings":[
// the bindings for this function
]
}
// lots of other properties (mostly URIs)
}
I'm creating a management tool outside of the portal that will allow users to enable and disable functions.
Hoping I can avoid creating the JSON payload by hand, so I'm wondering if there is something in an SDK (WebJobs??) that has this functionality.
Further to #James Z.'s answer, I've created the following class in C# that allows you to programmatically disable / enable an Azure function.
The functionsSiteRoot constructor argument is the Kudu root of your Functions application, eg https://your-functions-web-app.scm.azurewebsites.net/api/vfs/site/wwwroot/
The username and password can be obtained from "Get publish profile" in the App Service settings for your Functions.
public class FunctionsHelper : IFunctionsHelper
{
private readonly string _username;
private readonly string _password;
private readonly string _functionsSiteRoot;
private WebClient _webClient;
public FunctionsHelper(string username, string password, string functionsSiteRoot)
{
_username = username;
_password = password;
_functionsSiteRoot = functionsSiteRoot;
_webClient = new WebClient
{
Headers = { ["ContentType"] = "application/json" },
Credentials = new NetworkCredential(username, password),
BaseAddress = functionsSiteRoot
};
}
public void StopFunction(string functionName)
{
SetFunctionState(functionName, isDisabled: true);
}
public void StartFunction(string functionName)
{
SetFunctionState(functionName, isDisabled: false);
}
private void SetFunctionState(string functionName, bool isDisabled)
{
var functionJson =
JsonConvert.DeserializeObject<FunctionSettings>(_webClient.DownloadString(GetFunctionJsonUrl(functionName)));
functionJson.disabled = isDisabled;
_webClient.Headers["If-Match"] = "*";
_webClient.UploadString(GetFunctionJsonUrl(functionName), "PUT", JsonConvert.SerializeObject(functionJson));
}
private static string GetFunctionJsonUrl(string functionName)
{
return $"{functionName}/function.json";
}
}
internal class FunctionSettings
{
public bool disabled { get; set; }
public List<Binding> bindings { get; set; }
}
internal class Binding
{
public string name { get; set; }
public string type { get; set; }
public string direction { get; set; }
public string queueName { get; set; }
public string connection { get; set; }
public string accessRights { get; set; }
}
No, this is not possible currently. The disabled metadata property in function.json is what determines whether a function is enabled. The portal just updates that value when you enable/disable in the portal.
Not sure if it will meet your needs, but I'll point out that there is also a host.json functions array that can be used to control the set of functions that will be loaded (documented here). So for example, if you only wanted 2 of your 10 functions enabled, you could set this property to an array containing only those 2 function names (e.g. "functions": [ "QueueProcessor", "GitHubWebHook" ]), and only those will be loaded/enabled. However, this is slightly different than enable/disable in that you won't be able to invoke the excluded functions via the portal, whereas you can portal invoke disabled functions.
Further to #DavidGouge 's answer above, the code he posted does work, I just tested it and will be using it in my app. However it needs a couple of tweaks:
Remove the inheritance from IFunctionsHelper. I'm not sure what that interface is but it wasn't required.
Change the class definition for Binding as follows:
internal class Binding
{
public string name { get; set; }
public string type { get; set; }
public string direction { get; set; }
public string queueName { get; set; }
public string connection { get; set; }
public string accessRights { get; set; }
public string schedule { get; set; }
}
After that it would work.
P.S. I would have put this as a comment on the original answer, but I don't have enough reputation on Stack Overflow to post comments!
Using a combination of #Satya V's and #DavidGouge's solutions, I came up with this:
public class FunctionsHelper
{
private readonly ClientSecretCredential _tokenCredential;
private readonly HttpClient _httpClient;
public FunctionsHelper(string tenantId, string clientId, string clientSecret, string subscriptionId, string resourceGroup, string functionAppName)
{
var baseUrl =
$"https://management.azure.com/subscriptions/{subscriptionId}/resourceGroups/{resourceGroup}/providers/Microsoft.Web/sites/{functionAppName}/";
var httpClient = new HttpClient
{
BaseAddress = new Uri(baseUrl)
};
_httpClient = httpClient;
_tokenCredential = new ClientSecretCredential(tenantId, clientId, clientSecret);
}
private async Task SetAuthHeader()
{
var accessToken = await GetAccessToken();
_httpClient.DefaultRequestHeaders.Authorization = AuthenticationHeaderValue.Parse($"Bearer {accessToken}");
}
private async Task<string> GetAccessToken()
{
return (await _tokenCredential.GetTokenAsync(
new TokenRequestContext(new[] {"https://management.azure.com/.default"}))).Token;
}
public async Task StopFunction(string functionName)
{
await SetFunctionState(functionName, isDisabled: true);
}
public async Task StartFunction(string functionName)
{
await SetFunctionState(functionName, isDisabled: false);
}
private async Task SetFunctionState(string functionName, bool isDisabled)
{
await SetAuthHeader();
var appSettings = await GetAppSettings();
appSettings.properties[$"AzureWebJobs.{functionName}.Disabled"] = isDisabled ? "1" : "0";
var payloadJson = JsonConvert.SerializeObject(new
{
kind = "<class 'str'>", appSettings.properties
});
var stringContent = new StringContent(payloadJson, Encoding.UTF8, "application/json");
await _httpClient.PutAsync("config/appsettings?api-version=2019-08-01", stringContent);
}
private async Task<AppSettings> GetAppSettings()
{
var res = await _httpClient.PostAsync("config/appsettings/list?api-version=2019-08-01", null);
var content = await res.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<AppSettings>(content);
}
}
internal class AppSettings
{
public Dictionary<string, string> properties { get; set; }
}
The problem with using the Kudu api to update the function.json file is that it will be overwritten on any subsequent deploy. This uses Azure's Rest Api to update the Configuration of the application. You will first need an Azure Service Principle to use the api though.
Using the Azure Cli, you can run az ad sp create-for-rbac to generate the Service Principle and get the client id and client secret. Because the UpdateConfiguration endpoint does not allow you to update a single value, and overwrites the entire Configuration object with the new values, you must first get all the current Configuration values, update the one you want, and then call the Update endpoint with the new Configuration keys and values.
I would imagine you can use Kudu REST API (specifically VFS) to update the disabled metadata property in function.json. Would that disable the function?
Here is the Kudu REST API. https://github.com/projectkudu/kudu/wiki/REST-API
The CLI command That is used to disable the Azure function through CLI - documented here
az functionapp config appsettings set --name <myFunctionApp> \
--resource-group <myResourceGroup> \
--settings AzureWebJobs.QueueTrigger.Disabled=true
I had captured fiddler while while running the above command.
Azure CLI works on the Python process The python process was issuing request to
https://management.azure.com to update appsetting.
got a reference to the same endpoint in the below REST Endpoint :
https://learn.microsoft.com/en-us/rest/api/appservice/webapps/updateapplicationsettings
Request URI :
PUT
https://management.azure.com/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Web/sites/{name}/config/appsettings?api-version=2019-08-01
Headers :
Authorization: Bearer <> ;
Content-Type: application/json; charset=utf-8
Request Body:
{"kind": "<class 'str'>", "properties":JSON}
We can hardcode the properties or get it dynamically. For disabling the function, will have to update the JSON node of Properties : Azure.WebJobs.QueueTrigger.Disabled = True
To get properties you could use the endpoint, you could refer Web Apps - List Application Settings
The Output looks up as below :
Hope this helps :)
What about this: https://learn.microsoft.com/en-us/azure/azure-functions/disable-function?tabs=portal#localsettingsjson
This looks like the easiest solution for local development.
I have a web api, where the global configuration is configured to use:
XmlMediaTypeFormatter
My problem is I wont to extend this web api with a new controller, that uses the JsonMediaTypeFormatter instead.
Is it possible to change the MediaTypeFormatter to JSON for only one API Controller class?
My problem is not returning JSON, I have accumplished this with returning HttpResponseMessage:
return new HttpResponseMessage
{
Content = new ObjectContent<string>("Hello world", new JsonMediaTypeFormatter()),
StatusCode = HttpStatusCode.OK
};
It's on the request I get the problem. If I have an object with two properties:
public class VMRegistrant
{
public int MerchantId { get; set; }
public string Email { get; set; }
}
And my controller action takes the VMRegistrant as argument:
public HttpResponseMessage CreateRegistrant(VMRegistrant registrant)
{
// Save registrant in db...
}
But the problem is when I call the action with JSON it fails.
You can have your controller return an IHttpActionResult and use the extension method HttpRequestMessageExtensions.CreateResponse<T> and specify the formatter you want to use:
public IHttpActionResult Foo()
{
var bar = new Bar { Message = "Hello" };
return Request.CreateResponse(HttpStatusCode.OK, bar, new MediaTypeHeaderValue("application/json"));
}
Another possibility is to use the ApiController.Content method:
public IHttpActionResult Foo()
{
var bar = new Bar { Message = "Hello" };
return Content(HttpStatusCode.OK, bar, new JsonMediaTypeFormatter(), new MediaTypeHeaderValue("application/json"));
}
Edit:
One possibility is to read and deserialize the content yourself from the Request object via reading from the stream and using a JSON parser such as Json.NET to create the object from JSON:
public async Task<IHttpActionResult> FooAsync()
{
var json = await Request.Content.ReadAsStringAsync();
var content = JsonConvert.DeserializeObject<VMRegistrant>(json);
}
Yes, it's possible to change the MediaTypeFormatters for only one class/controller. If you want to save and restore the default formatters you can follow these steps:
In the beginning of the request save old formatters
Clear the formatters collection
Add the desired formatter
At the end of the request copy back the old formatters
I think this is easily done by an ActionFilterAttribute:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)]
public class ChangeFormatterAttribute : ActionFilterAttribute
{
private IEnumerable<MediaTypeFormatter> oldFormatters;
private MediaTypeFormatter desiredFormatter;
public ChangeFormatterAttribute(Type formatterType)
{
this.desiredFormatter = Activator.CreateInstance(formatterType) as MediaTypeFormatter;
}
public override void OnActionExecuting(HttpActionContext actionContext)
{
var formatters = actionContext.ControllerContext.Configuration.Formatters;
oldFormatters = formatters.ToList();
formatters.Clear();
formatters.Add(desiredFormatter);
base.OnActionExecuting(actionContext);
}
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
var formatters = actionExecutedContext.ActionContext.ControllerContext.Configuration.Formatters;
formatters.Clear();
formatters.AddRange(oldFormatters);
base.OnActionExecuted(actionExecutedContext);
}
}
And the usage:
[ChangeFormatterAttribute(typeof(JsonMediaTypeFormatter))]
public class HomeController : ApiController
{
public string Get()
{
return "ok";
}
}
// ...
[ChangeFormatterAttribute(typeof(XmlMediaTypeFormatter))]
public class ValuesController : ApiController
{
public string Get()
{
return "ok";
}
}
Maybe you could have your media type formatter only accept the type that is handled by your controller:
public class Dog
{
public string Name { get; set; }
}
public class DogMediaTypeFormatter : JsonMediaTypeFormatter
{
public override bool CanReadType(Type type)
{
return type == typeof (Dog);
}
}
Probably not the best solution though :I