I'm trying to do some very simple request throttling on my ASP.NET web project. Currently I'm not interested in globally throttling requests against DOS attacks, but would like to artificially delay the reponse to all login attempts, just to make dictionary attacks a bit harder to do (more or less like Jeff Atwood outlined here).
How would you implement it? The näive way of doing it would be - I suppose - to simply call
Thread.Sleep();
somewhere during the request. Suggestions? :)
I got the same idea as you on how to improve the security of a login screen (and password reset screens). I'm going to implement this for my project and I'll share my story with you.
Requirements
My requirements are in following points:
Do not block individual users just because someone is trying to hack in
My user names are very easy to guess because they follow a certain pattern (and I do not like security by obscurity)
Do not waste server resources by sleeping on too many requests, the queue would overflow eventually and requests would start timing out
Provide a swift service to most users 99% of the time
Eliminate brute force attacks on the login screen
Handle distributed attacks as well
Needs to be reasonably thread-safe
Plan
So we shall have a list of failed attempts and their time stamp. Every time we have a login attempt, we'll check this list and the more there are failed attempts, the longer it will take to login. Each time we'll prune old entries by their time stamp. Beyond a certain threshold, no logins will be allowed and all login requests will be failed immediately (attack emergency shut-down).
We do not stop with the automatic protection. A notification should be sent to admins in case of the emergency shut-down so that the incident can be investigated and reparation measures can be taken. Our logs should hold a firm record of the failed attempts including the time, user name and source IP address for investigation.
The plan is to implement this as a statically declared queue, where failed attempts enqueue and old entries dequeue. the length of the queue is our indicator of severity. When I've got the code ready, I'll update the answer. I might include Keltex's suggestion too - releasing the response quickly and completing the login with another request.
Update: There is two things missing:
The redirect of the response to a wait page not to clog the request queue and that is a little biggie obviously. We need to give the user a token to check back later with another request. This could be another security hole so we need to be extremely cautious about handling this. Or just drop that Thread.Sleap(xxx) in the Action method :)
The IP, dooh, next time...
Let's see if we can get through that eventually...
What's done
ASP.NET page
ASP.NET UI Page should have minimum hassle, then we get an instance of a Gate like this:
static private LoginGate Gate = SecurityDelayManager.Instance.GetGate<LoginGate>();
And after login (or password reset) attempt, call:
SecurityDelayManager.Instance.Check(Gate, Gate.CreateLoginAttempt(success, UserName));
ASP.NET handling code
The LoginGate is implemented inside the AppCode of the ASP.NET project so it has access to all the front-end goodies. It implements the interface IGate which is used by the backend SecurityDelayManager instance. The Action method needs to be completed with wait redirection.
public class LoginGate : SecurityDelayManager.IGate
{
#region Static
static Guid myID = new Guid("81e19a1d-a8ec-4476-a187-3130361a9006");
static TimeSpan myTF = TimeSpan.FromHours(24);
#endregion
#region Private Types
class LoginAttempt : Attempt { }
class PasswordResetAttempt : Attempt { }
class PasswordResetRequestAttempt : Attempt { }
abstract class Attempt : SecurityDelayManager.IAttempt
{
public bool Successful { get; set; }
public DateTime Time { get; set; }
public String UserName { get; set; }
public string SerializeForAuditLog()
{
return ToString();
}
public override string ToString()
{
return String.Format("{2} Successful:{0} #{1}", Successful, Time, GetType().Name);
}
}
#endregion
#region Attempt creation utility methods
public SecurityDelayManager.IAttempt CreateLoginAttempt(bool success, string userName)
{
return new LoginAttempt() { Successful = success, UserName = userName, Time = DateTime.Now };
}
public SecurityDelayManager.IAttempt CreatePasswordResetAttempt(bool success, string userName)
{
return new PasswordResetAttempt() { Successful = success, UserName = userName, Time = DateTime.Now };
}
public SecurityDelayManager.IAttempt CreatePasswordResetRequestAttempt(bool success, string userName)
{
return new PasswordResetRequestAttempt() { Successful = success, UserName = userName, Time = DateTime.Now };
}
#endregion
#region Implementation of SecurityDelayManager.IGate
public Guid AccountID { get { return myID; } }
public bool ConsiderSuccessfulAttemptsToo { get { return false; } }
public TimeSpan SecurityTimeFrame { get { return myTF; } }
public SecurityDelayManager.ActionResult Action(SecurityDelayManager.IAttempt attempt, int attemptsCount)
{
var delaySecs = Math.Pow(2, attemptsCount / 5);
if (delaySecs > 30)
{
return SecurityDelayManager.ActionResult.Emergency;
}
else if (delaySecs < 3)
{
return SecurityDelayManager.ActionResult.NotDelayed;
}
else
{
// TODO: Implement the security delay logic
return SecurityDelayManager.ActionResult.Delayed;
}
}
#endregion
}
Backend somewhat thread-safe management
So this class (in my core lib) will handle the multi-threaded counting of attempts:
/// <summary>
/// Helps to count attempts and take action with some thread safety
/// </summary>
public sealed class SecurityDelayManager
{
ILog log = LogManager.GetLogger(typeof(SecurityDelayManager).FullName + ".Log");
ILog audit = LogManager.GetLogger(typeof(SecurityDelayManager).FullName + ".Audit");
#region static
static SecurityDelayManager me = new SecurityDelayManager();
static Type igateType = typeof(IGate);
public static SecurityDelayManager Instance { get { return me; } }
#endregion
#region Types
public interface IAttempt
{
/// <summary>
/// Is this a successful attempt?
/// </summary>
bool Successful { get; }
/// <summary>
/// When did this happen
/// </summary>
DateTime Time { get; }
String SerializeForAuditLog();
}
/// <summary>
/// Gate represents an entry point at wich an attempt was made
/// </summary>
public interface IGate
{
/// <summary>
/// Uniquely identifies the gate
/// </summary>
Guid AccountID { get; }
/// <summary>
/// Besides unsuccessful attempts, successful attempts too introduce security delay
/// </summary>
bool ConsiderSuccessfulAttemptsToo { get; }
TimeSpan SecurityTimeFrame { get; }
ActionResult Action(IAttempt attempt, int attemptsCount);
}
public enum ActionResult { NotDelayed, Delayed, Emergency }
public class SecurityActionEventArgs : EventArgs
{
public SecurityActionEventArgs(IGate gate, int attemptCount, IAttempt attempt, ActionResult result)
{
Gate = gate; AttemptCount = attemptCount; Attempt = attempt; Result = result;
}
public ActionResult Result { get; private set; }
public IGate Gate { get; private set; }
public IAttempt Attempt { get; private set; }
public int AttemptCount { get; private set; }
}
#endregion
#region Fields
Dictionary<Guid, Queue<IAttempt>> attempts = new Dictionary<Guid, Queue<IAttempt>>();
Dictionary<Type, IGate> gates = new Dictionary<Type, IGate>();
#endregion
#region Events
public event EventHandler<SecurityActionEventArgs> SecurityAction;
#endregion
/// <summary>
/// private (hidden) constructor, only static instance access (singleton)
/// </summary>
private SecurityDelayManager() { }
/// <summary>
/// Look at the attempt and the history for a given gate, let the gate take action on the findings
/// </summary>
/// <param name="gate"></param>
/// <param name="attempt"></param>
public ActionResult Check(IGate gate, IAttempt attempt)
{
if (gate == null) throw new ArgumentException("gate");
if (attempt == null) throw new ArgumentException("attempt");
// get the input data befor we lock(queue)
var cleanupTime = DateTime.Now.Subtract(gate.SecurityTimeFrame);
var considerSuccessful = gate.ConsiderSuccessfulAttemptsToo;
var attemptSuccessful = attempt.Successful;
int attemptsCount; // = ?
// not caring too much about threads here as risks are low
Queue<IAttempt> queue = attempts.ContainsKey(gate.AccountID)
? attempts[gate.AccountID]
: attempts[gate.AccountID] = new Queue<IAttempt>();
// thread sensitive - keep it local and short
lock (queue)
{
// maintenance first
while (queue.Count != 0 && queue.Peek().Time < cleanupTime)
{
queue.Dequeue();
}
// enqueue attempt if necessary
if (!attemptSuccessful || considerSuccessful)
{
queue.Enqueue(attempt);
}
// get the queue length
attemptsCount = queue.Count;
}
// let the gate decide what now...
var result = gate.Action(attempt, attemptsCount);
// audit log
switch (result)
{
case ActionResult.Emergency:
audit.ErrorFormat("{0}: Emergency! Attempts count: {1}. {2}", gate, attemptsCount, attempt.SerializeForAuditLog());
break;
case ActionResult.Delayed:
audit.WarnFormat("{0}: Delayed. Attempts count: {1}. {2}", gate, attemptsCount, attempt.SerializeForAuditLog());
break;
default:
audit.DebugFormat("{0}: {3}. Attempts count: {1}. {2}", gate, attemptsCount, attempt.SerializeForAuditLog(), result);
break;
}
// notification
if (SecurityAction != null)
{
var ea = new SecurityActionEventArgs(gate, attemptsCount, attempt, result);
SecurityAction(this, ea);
}
return result;
}
public void ResetAttempts()
{
attempts.Clear();
}
#region Gates access
public TGate GetGate<TGate>() where TGate : IGate, new()
{
var t = typeof(TGate);
return (TGate)GetGate(t);
}
public IGate GetGate(Type gateType)
{
if (gateType == null) throw new ArgumentNullException("gateType");
if (!igateType.IsAssignableFrom(gateType)) throw new Exception("Provided gateType is not of IGate");
if (!gates.ContainsKey(gateType) || gates[gateType] == null)
gates[gateType] = (IGate)Activator.CreateInstance(gateType);
return gates[gateType];
}
/// <summary>
/// Set a specific instance of a gate for a type
/// </summary>
/// <typeparam name="TGate"></typeparam>
/// <param name="gate">can be null to reset the gate for that TGate</param>
public void SetGate<TGate>(TGate gate) where TGate : IGate
{
var t = typeof(TGate);
SetGate(t, gate);
}
/// <summary>
/// Set a specific instance of a gate for a type
/// </summary>
/// <param name="gateType"></param>
/// <param name="gate">can be null to reset the gate for that gateType</param>
public void SetGate(Type gateType, IGate gate)
{
if (gateType == null) throw new ArgumentNullException("gateType");
if (!igateType.IsAssignableFrom(gateType)) throw new Exception("Provided gateType is not of IGate");
gates[gateType] = gate;
}
#endregion
}
Tests
And I've made a test fixture for that:
[TestFixture]
public class SecurityDelayManagerTest
{
static MyTestLoginGate gate;
static SecurityDelayManager manager;
[SetUp]
public void TestSetUp()
{
manager = SecurityDelayManager.Instance;
gate = new MyTestLoginGate();
manager.SetGate(gate);
}
[TearDown]
public void TestTearDown()
{
manager.ResetAttempts();
}
[Test]
public void Test_SingleFailedAttemptCheck()
{
var attempt = gate.CreateLoginAttempt(false, "user1");
Assert.IsNotNull(attempt);
manager.Check(gate, attempt);
Assert.AreEqual(1, gate.AttemptsCount);
}
[Test]
public void Test_AttemptExpiration()
{
var attempt = gate.CreateLoginAttempt(false, "user1");
Assert.IsNotNull(attempt);
manager.Check(gate, attempt);
Assert.AreEqual(1, gate.AttemptsCount);
}
[Test]
public void Test_SingleSuccessfulAttemptCheck()
{
var attempt = gate.CreateLoginAttempt(true, "user1");
Assert.IsNotNull(attempt);
manager.Check(gate, attempt);
Assert.AreEqual(0, gate.AttemptsCount);
}
[Test]
public void Test_ManyAttemptChecks()
{
for (int i = 0; i < 20; i++)
{
var attemptGood = gate.CreateLoginAttempt(true, "user1");
manager.Check(gate, attemptGood);
var attemptBaad = gate.CreateLoginAttempt(false, "user1");
manager.Check(gate, attemptBaad);
}
Assert.AreEqual(20, gate.AttemptsCount);
}
[Test]
public void Test_GateAccess()
{
Assert.AreEqual(gate, manager.GetGate<MyTestLoginGate>(), "GetGate should keep the same gate");
Assert.AreEqual(gate, manager.GetGate(typeof(MyTestLoginGate)), "GetGate should keep the same gate");
manager.SetGate<MyTestLoginGate>(null);
var oldGate = gate;
var newGate = manager.GetGate<MyTestLoginGate>();
gate = newGate;
Assert.AreNotEqual(oldGate, newGate, "After a reset, new gate should be created");
manager.ResetAttempts();
Test_ManyAttemptChecks();
manager.SetGate(typeof(MyTestLoginGate), oldGate);
manager.ResetAttempts();
Test_ManyAttemptChecks();
}
}
public class MyTestLoginGate : SecurityDelayManager.IGate
{
#region Static
static Guid myID = new Guid("81e19a1d-a8ec-4476-a187-5130361a9006");
static TimeSpan myTF = TimeSpan.FromHours(24);
class LoginAttempt : Attempt { }
class PasswordResetAttempt : Attempt { }
abstract class Attempt : SecurityDelayManager.IAttempt
{
public bool Successful { get; set; }
public DateTime Time { get; set; }
public String UserName { get; set; }
public string SerializeForAuditLog()
{
return ToString();
}
public override string ToString()
{
return String.Format("Attempt {2} Successful:{0} #{1}", Successful, Time, GetType().Name);
}
}
#endregion
#region Test properties
public int AttemptsCount { get; private set; }
#endregion
#region Implementation of SecurityDelayManager.IGate
public Guid AccountID { get { return myID; } }
public bool ConsiderSuccessfulAttemptsToo { get { return false; } }
public TimeSpan SecurityTimeFrame { get { return myTF; } }
public SecurityDelayManager.IAttempt CreateLoginAttempt(bool success, string userName)
{
return new LoginAttempt() { Successful = success, UserName = userName, Time = DateTime.Now };
}
public SecurityDelayManager.IAttempt CreatePasswordResetAttempt(bool success, string userName)
{
return new PasswordResetAttempt() { Successful = success, UserName = userName, Time = DateTime.Now };
}
public SecurityDelayManager.ActionResult Action(SecurityDelayManager.IAttempt attempt, int attemptsCount)
{
AttemptsCount = attemptsCount;
return attemptsCount < 3
? SecurityDelayManager.ActionResult.NotDelayed
: attemptsCount < 30
? SecurityDelayManager.ActionResult.Delayed
: SecurityDelayManager.ActionResult.Emergency;
}
#endregion
}
I would place the delay on the server validation portion where it won't attempt to validate (come back automatically as false have a message saying the user has to wait so many seconds before making another attempt). another answer until so many seconds have passed. Doing the thread.sleep will prevent one browser from making another attempt, but it won't stop a distributed attack where someone has multiple programs trying to login as the user simultaneously.
Another possibility is that the time between attempts varies by how many attempts are made to login. So the second attempt they have a one second wait, the third is maybe 2, the third is 4 and so on. That way you don't have a legitimate user having to wait 15 seconds between login attempts because they mistyped their password incorrectly the first time.
Kevin makes a good point about not wanting to tie up your request thread. One answer would be to make the login an asychronous request. The asychronous process would just be to wait for the amount of time you choose (500ms?). Then you wouldn't block the request thread.
I don't think this will help you thwart DOS attacks. If you sleep the request thread, you are still allowing the request to occupy your thread pool and still allow the attacker to bring your web service to its knees.
Your best bet may be to lock out requests after a specified number of failed attempts based on the attempted login name, source IP, etc, to try and target the source of the attack without detriment to your valid users.
I know it's not what you're asking, but you could implement an account lockout instead. That way, you give them their guesses and then can make them wait any amount of time you want before they can start guessing again. :)
I don't think what you are asking for is quite an efficient way in a web enviornment. Login screens' purpose is to provide an easy way for 'users' to gain access to your services and should be easy and fast to use. So you should not make a user wait considering 99% of the them will not be bad-minded.
Sleep.Trhead also has the potential to place a huge load on your server should there be a lot of concurrent users trying to log in. Potential options would be:
Block the IP for (e.g.) the end of the session for x number of unsuccessful login attempts
Provide a captcha
of course these are not all the options but still I am sure more people will have more ideas...
Related
I have already followed the guide here. I have tried both the config and "in code" approach of initializing and registering our telemetry processor. My goal is to filter out some HTTP responses so that those don't make their way to the sampled data. I haven't had any success. While our processor is initialized on app start, the Process method is never hit. Also, I already made sure that there is an InstrumentationKey in the config and that I'm using the correct key. What else am I missing?
This is what I have:
public class MyTelemetryProcessor : ITelemetryProcessor
{
private ITelemetryProcessor Next { get; set; }
// You can pass values from .config
public string MyParamFromConfigFile { get; set; }
// Link processors to each other in a chain.
public MyTelemetryProcessor(ITelemetryProcessor next)
{
this.Next = next; <-- this is always hit indicating this processor is active
}
public void Process(ITelemetry item)
{
// To filter out an item, just return
if (!OKtoSend(item)) { return; } <-- breakpoint here is never hit
// Modify the item if required
ModifyItem(item);
this.Next.Process(item);
}
private bool OKtoSend(ITelemetry item) <-- and consequently this method is never hit
{
var request = item as RequestTelemetry; <-- breakpoint here is never hit
// some more code goes here
return request.Success.GetValueOrDefault(false);
}
// Example: replace with your own modifiers.
private void ModifyItem(ITelemetry item)
{
item.Context.Properties.Add("app-version", "1." + MyParamFromConfigFile);
}
}
And this is how it is registered. I can see this being hit during debugging when the app starts up:
var builder = TelemetryConfiguration.Active.TelemetryProcessorChainBuilder;
builder.Use((next) => new MyTelemetryProcessor (next));
builder.Build();
In aspnetcore, my solution was to use :
services.AddApplicationInsightsTelemetryProcessor(typeof(BasicTelemetryFilter));
(using the regular CreateWebHostBuilder :
WebHost.CreateDefaultBuilder(args)
.UseApplicationInsights()
.UseStartup<Startup>();
)
i have a method with the following signature that belongs to a class.
public virtual IAsyncResult DoSomething(CustomOptions options);
I am trying to figure out how exactly do i call the callback.I cannot find anywhere the event on which to give a callback method.
This is copied pretty much from MSDN:
// Initiate the asychronous call.
IAsyncResult result = caller.BeginInvoke(3000,
out threadId, null, null);
Thread.Sleep(0);
Console.WriteLine("Main thread {0} does some work.",
Thread.CurrentThread.ManagedThreadId);
// Wait for the WaitHandle to become signaled.
result.AsyncWaitHandle.WaitOne();
// Perform additional processing here.
// Call EndInvoke to retrieve the results.
string returnValue = caller.EndInvoke(out threadId, result);
If the method is your own, you might want to try returning a Task, which will have a ContinueWith method, that takes a block of code (Another Task) as a callback, which will run once the Task has completed.
To understand how to use IAsyncResult, you should understand where it would be use. It's normally used for asynchronous call. The most common usage is delegate asynchronous call. In that case, IAsyncResult is a receipt, it is used as an "Infomation carrier", and provides a synchronization object in order to abort the thread when asynchronous operation completes.
Usually you don't need to create an IAsyncResult. IAsyncResult is just a way to implement receipt function. You may not make it so complicated. Just transmit a simple struct to carry infomations you need.
like:
/// <summary>
/// This is a simplified IAsyncResult
/// </summary>
public class Receipt
{
/// <summary>
/// Name
/// </summary>
public String Name
{
get;
set;
}
/// <summary>
/// Age
/// </summary>
public Byte Age
{
get;
set;
}
public String OperationResultText
{
get;
set;
}
}
public class Test
{
public delegate void Async_OperationCallbackHandler(Receipt r);
public void MainEntry()
{
Thread tmpThread = new Thread(()=>
{
Async_Operation("ASDF", 20, Async_Operation_Callback);
});
tmpThread.Start();
}
public void Async_Operation(String name, Byte age, Async_OperationCallbackHandler callback)
{
//Do something with "name" and "age" and get result...
String result = "OK...";
Receipt r = new Receipt()
{
Age = age,
Name = name,
OperationResultText = result
};
callback(r);
}
internal void Async_Operation_Callback(Receipt r)
{
Console.WriteLine("Name = " + r.Name + ", Age = " + r.Age + ", Operation result: " + r.OperationResultText);
}
}
Of course, I did not consider synchronization. But .NET Framework has taken into it. So determine the contents of receipt according to your needs, not need to use IAsyncResult.
See:
Calling Synchronous Methods Asynchronously
IAsyncResult Interface
I've created a custom activity which contains as a Body another Activity.
[Browsable(false)]
public Activity Body { get; set; }
protected override void Execute(NativeActivityContext context)
{
ActivityInstance res = context.ScheduleActivity(Body, new CompletionCallback(OnExecuteComplete), OnFaulted);
}
private void OnFaulted(NativeActivityFaultContext faultContext, Exception propagatedException, ActivityInstance propagatedFrom)
{
throw new Exception(propagatedException.Message);
}
When an exception is thrown during the execution of the Body, ma handler for the OnFaulted is hit.
My execution starts with a call to static method Run of the WorkflowApplication class. My WorkflowApplication instance has a handler associated for the OnUnhandledException event.
instance.OnUnhandledException +=
delegate(WorkflowApplicationUnhandledExceptionEventArgs args)
{
Console.WriteLine(args.ExceptionSource);
waitEvent.Set();
return UnhandledExceptionAction.Cancel;
};
But regardless of what happens when the Activity hosted in the Body is executed, i never reach the handler defined above. I thought that if i throw an exception from the OnFaulted, i will be able to redirect the flow to the OnUnhandledException but i was wrong. Any ideas ?
I need this in order to centralize my errors, check them and display messages accordingly. Also i need a way to stop the execution and so on and i don't want to define handlers all over the application. Is there any way to accomplish this ?
As Will suggested, i will post what i did to handle my scenario.
Basically, in my custom activity i have hosted an Assign :
[Browsable(false)]
public Activity Body { get; set; }
Activity System.Activities.Presentation.IActivityTemplateFactory.Create(System.Windows.DependencyObject target)
{
return new Assignment()
{
Body = new Assign() { DisplayName = "" }
};
}
I've added this code to my Execute method :
ActivityInstance res = context.ScheduleActivity(Body, new CompletionCallback(OnExecuteComplete), OnFaulted);
I was trying to run this Assignment by giving an array a negative value as index and and an exception was thrown. This, somehow ended my execution but no handler for the events of my WorkflowApplication instance were hit.
Here is the method given as a callback when executing the body ( in our case the Assign activity ) :
private void OnFaulted(NativeActivityFaultContext faultContext, Exception propagatedException, ActivityInstance propagatedFrom)
{
faultContext.HandleFault();
CommunicationExtension ce = faultContext.GetExtension<CommunicationExtension>();
ITextExpression toTextExpression = (propagatedFrom.Activity as Assign).To.Expression as ITextExpression;
string valueTextExpression = string.Empty;
if ((propagatedFrom.Activity as Assign).Value != null)
{
if ((propagatedFrom.Activity as Assign).Value.Expression != null)
valueTextExpression = (propagatedFrom.Activity as Assign).Value.Expression.ToString();
}
if (ce != null)
{
ce.AddData(string.Format("{0} found on Assignment definition [{1} = {2}]", propagatedException.Message, toTextExpression.ExpressionText, valueTextExpression));
}
}
The trick was to call :
faultContext.HandleFault();
and use CommunicationExtension to allow me to to display the erros in the GUI.
The code for this class is trivial :
public class CommunicationExtension
{
public List<string> Messages { get; set; }
public CommunicationExtension()
{
Messages = new List<string>();
}
public void AddData(string message)
{
if (string.IsNullOrEmpty(message))
return;
Messages.Add(message);
}
}
Use this to add the extension:
CommunicationExtension ce = new CommunicationExtension();
instance.Extensions.Add(ce);
where instance is my WorkflowApplication instance.
I understood that for each instance of the workflow application we have one instance of its extension class. So i can send messages like this from all my custom activities in order to display their status.
I hope this scenario can help other people too.
I'm having a problem with Web API 2 (.net 4.5.1) in that it seems to ignore PATCH requests where the property is an integer, but processes other types without a problem (I've tested string and decimal).
I’ve setup an unsecured test API with a 'products' controller at http://playapi.azurewebsites.net/api/products. If you do a GET to that URL, you’ll get something like this product back:
{"Id": 1,"Name": "Xbox One","Category": "gaming","Price": 300,"Stock": 5}
‘Name’ and ‘Category’ are both strings, ‘Price’ is a Decimal and ‘Stock’ is an Integer.
If you send these requests, they both work (You’ll get a 200/OK with the updated entity):
PATCH, http://playapi.azurewebsites.net/api/products/1 with {"Price": 600.00}
PATCH, http://playapi.azurewebsites.net/api/products/1 with
{"Category": "Electronics"}
However, if you send this, it returns 200/OK, but does not make the update and the stock remains at the original value
PATCH, http://playapi.azurewebsites.net/api/products/1 with
{"Stock": 4}
My controller code is fairly standard boiler plate code (from the scaffolded ODATA controller but moved into a standard API controller):
// PATCH: api/Products/5
[AcceptVerbs("PATCH", "MERGE")]
public async Task<IHttpActionResult> PatchOrder(int id, Delta<Product> patch)
{
Validate(patch.GetEntity());
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var item = await db.Products.FindAsync(id);
if (item == null)
{
return NotFound();
}
patch.Patch(item);
try
{
await db.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!ProductExists(id))
{
return NotFound();
}
else
{
throw;
}
}
return Ok(item);
}
My model for 'Product' is as follows:
namespace PlayAPI.Models
{
public class Product
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public string Category { get; set; }
public double Price { get; set; }
public int Stock { get; set; }
}
}
When I debug the controller, I see that the ‘patch’ object has a _changedProperties collection which has no items in it when I do an integer request, but when I do any other kind of request it has the key that I changed.
Should web API support PATCH requests for integer properties? If so, do I need to do anything special on the server or client to make it work?
As a quick fix, Change the int to an Int64 on PlayAPI.Models.Product.
public Int64 Stock { get; set; }
It's my understanding that The Delta object used to patch the existing object doesn’t use JSON.net to convert and is silently throwing an Invalid cast exception when it parses JSON and then compares to the existing object from your database. You can read more about the bug over here: http://aspnetwebstack.codeplex.com/workitem/777
If you can't actually change the data type successfully, there may be a decent hack fix that you can use. Just attached unreadable data into the query string.
Here's a function you can call from within your Patch functions. As long as you aren't using the query string parameters specifically named what it's looking for, you should be just fine.
/// <summary>
/// Tries to attach additional parameters from the query string onto the delta object.
/// This uses the parameters extraInt32 and extraInt16, which can be used multiple times.
/// The parameter format is "PropertyName|Integer"
/// <para>Example: ?extraInt32=Prop1|123&extraInt16=Prop2|88&extraInt32=Prop3|null</para>
/// </summary>
[NonAction]
protected void SetAdditionalPatchIntegers<TEntity>(Delta<TEntity> deltaEntity, bool allowNull = true)
{
var queryParameters = Request.GetQueryNameValuePairs();
foreach (var param in queryParameters.Where(pair =>
pair.Key == "extraInt32" ||
pair.Key == "extraInt16"))
{
if (param.Value.Count(v => v == '|') != 1)
continue;
var splitParam = param.Value.Split('|');
if (allowNull &&
(String.IsNullOrWhiteSpace(splitParam[1]) ||
splitParam[1].Equals("null", StringComparison.OrdinalIgnoreCase)))
{
deltaEntity.TrySetPropertyValue(splitParam[0], null);
continue;
}
if (param.Key == "extraInt32")
{
int extraInt;
if (Int32.TryParse(splitParam[1], out extraInt))
{
deltaEntity.TrySetPropertyValue(splitParam[0], extraInt);
}
}
if (param.Key == "extraInt16")
{
short extraShort;
if (Int16.TryParse(splitParam[1], out extraShort))
{
deltaEntity.TrySetPropertyValue(splitParam[0], extraShort);
}
}
}
}
I really hate that there isn't a better answer, but at least something can be done about it.
I am new to MVVM and have written a small app to test the waters and get familiar with the pattern. The main function of my app takes too long to not have some sort of user feedback that the process is continuing along. What would be the recommended way to place the call in a separate thread and provide feedback for a progress bar? The ViewModel code for the function is below. Thanks for the help.
public DataView Data
{
get
{
return resultsView;
}
set
{
if (value == resultsView)
{
return;
}
resultsView = value;
RaisePropertyChanged("Data");
}
}
private void SetData()
{
Data = RetrieveData.GetPartData(SelectedTeam, SelectedYear).DefaultView;
}
public RelayCommand GetData
{
get;
private set;
}
/// <summary>
/// Initializes a new instance of the MainViewModel class.
/// </summary>
public MainViewModel()
{
// Initializers for other part of ViewModel
// Teams = RetrieveData.GetTeams();
// Years = RetrieveData.GetYears();
GetData = new RelayCommand(SetData);
}
I'm not completely versed on MVVM multi-threading yet, but this link seems quite do-able : WPF Multithreading: Using the BackgroundWorker and Reporting the Progress to the UI.
NOW, what I would try is something like this:
worker.DoWork += delegate(object s, DoWorkEventArgs args)
{
Data = RetrieveData.GetPartData(SelectedTeam, SelectedYear).DefaultView;
};