I have like that scenario:
I have an endpoint and this endpoint will save the requests in List or Queue in memory and it will return immediately success response to the consumer. This requirement is critical, the consumer should not wait for the responses, it will get responses from a different endpoint if it needs. So, this endpoint must return as quickly as possible after saving the request message in memory.
Another thread will distribute these requests to other endpoints and save the responses in memory as well.
What I did till now:
I created a controller api to save these requests in the memory. I saved them in a static request List like below:
public static class RequestList
{
public static event EventHandler<RequestEventArgs> RequestReceived;
private static List<DistributionRequest> Requests { get; set; } = new List<DistributionRequest>();
public static int RequestCount { get => RequestList.Requests.Count; }
public static DistributionRequest Add(DistributionRequest request)
{
request.RequestId = Guid.NewGuid().ToString();
RequestList.Requests.Add(request);
OnRequestReceived(new RequestEventArgs { Request = request });
return request;
}
public static bool Remove(DistributionRequest request) => Requests.Remove(request);
private static void OnRequestReceived(RequestEventArgs e)
{
RequestReceived?.Invoke(null, e);
}
}
public class RequestEventArgs : EventArgs
{
public DistributionRequest Request { get; set; }
}
And another class is subscribed to that event that exists in that static class and I am creating a new thread to make some background web requests to be able to achieve 2. item which I stated above.
private void RequestList_RequestReceived(object sender, RequestEventArgs e)
{
_logger.LogInformation($"Request Id: {e.Request.RequestId}, New request received");
Task.Factory.StartNew(() => Distribute(e.Request));
_logger.LogInformation($"Request Id: {e.Request.RequestId}, New task created for the new request");
//await Distribute(e.Request);
}
public async Task<bool> Distribute(DistributionRequest request)
{
//Some logic running here to send post request to different endpoints
//and to save results in memory
}
And here is my controller method:
[HttpPost]
public IActionResult Post([FromForm] DistributionRequest request)
{
var response = RequestList.Add(request);
return Ok(new DistributionResponse { Succeeded = true, RequestId = response.RequestId });
}
I tried that approach but it did not work as I expected, it should return within milliseconds since I am not waiting for responses but it seems to wait for something, and after every single request waiting time is increasing as below:
What am I doing wrong? Or Do you have a better idea? How can I achieve my goal?
Based on you example code I tried to implement it without "eventing". Therefore I get much better request times. I cannot say if this is related to your implementation or the eventing itself for this you have to do profiling.
I did it this way
RequestsController
Just like you had it in your example. Take the request and add it to the requests list.
[Route("requests")]
public class RequestsController : ControllerBase
{
private readonly RequestManager _mgr;
public RequestsController(RequestManager mgr)
{
_mgr = mgr;
}
[HttpPost]
public IActionResult AddRequest([FromBody] DistributionRequest request)
{
var item = _mgr.Add(request);
return Accepted(new { Succeeded = true, RequestId = item.RequestId });
}
}
RequestManager
Manage the request list and forward them to some distribor.
public class RequestManager
{
private readonly ILogger _logger;
private readonly RequestDistributor _distributor;
public IList<DistributionRequest> Requests { get; } = new List<DistributionRequest>();
public RequestManager(RequestDistributor distributor, ILogger<RequestManager> logger)
{
_distributor = distributor;
_logger = logger;
}
public DistributionRequest Add(DistributionRequest request)
{
_logger.LogInformation($"Request Id: {request.RequestId}, New request received");
/// Just add to the list of requests
Requests.Add(request);
/// Create and start a new task to distribute the request
/// forward it to the distributor.
/// Be sure to not add "await" here
Task.Factory.StartNew(() => _distributor.DistributeAsync(request));
_logger.LogInformation($"Request Id: {request.RequestId}, New task created for the new request");
return request;
}
}
RequestDistributor
Distribution logic can be implemented here
public class RequestDistributor
{
public async Task DistributeAsync(DistributionRequest request)
{
/// do your distribution here
/// currently just a mocked time range
await Task.Delay(5);
}
}
Wire up
... add all these things to your dependency injection configuration
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddSingleton<RequestDistributor>();
services.AddSingleton<RequestManager>();
}
Tests
With the here provided code pieces I received all the requests back in less than 10 ms.
Note
This is just an example try to always add interfaces to your services to make them testable ;).
Related
I am using the following attribute [ResponseCache(Duration = 60)] to cache a specific GET Request which is called a lot on my backend in .NET Core.
Everything is working fine except the cache isn't reloaded when some data in database has changed within the 60 seconds.
Is there a specific directive I have to set to reload/update the cache? link
Example Code Snippet from my Controller:
[HttpGet]
[ResponseCache(Duration = 60)]
public ActionResult<SomeTyp[]> SendDtos()
{
var dtos = _repository.QueryAll();
return Ok(dtos);
}
There is a solution with a usage of "ETag", "If-None-Match" HTTP headers. The idea is using a code which can give us an answer to the question: "Did action response changed?".
This can be done if a controller completely owns particular data lifetime.
Create ITagProvider:
public interface ITagProvider
{
string GetETag(string tagKey);
void InvalidateETag(string tagKey);
}
Create an action filter:
public class ETagActionFilter : IActionFilter
{
private readonly ITagProvider _tagProvider;
public ETagActionFilter(ITagProvider tagProvider)
{
_tagProvider = tagProvider ?? throw new ArgumentNullException(nameof(tagProvider));
}
public void OnActionExecuted(ActionExecutedContext context)
{
if (context.Exception != null)
{
return;
}
var uri = GetActionName(context.ActionDescriptor);
var currentEtag = _tagProvider.GetETag(uri);
if (!string.IsNullOrEmpty(currentEtag))
{
context.HttpContext.Response.Headers.Add("ETag", currentEtag);
}
}
public void OnActionExecuting(ActionExecutingContext context)
{
var uri = GetActionName(context.ActionDescriptor);
var requestedEtag = context.HttpContext.Request.Headers["If-None-Match"];
var currentEtag = _tagProvider.GetETag(uri);
if (requestedEtag.Contains(currentEtag))
{
context.HttpContext.Response.Headers.Add("ETag", currentEtag);
context.Result = new StatusCodeResult(StatusCodes.Status304NotModified);
}
}
private string GetActionName(ActionDescriptor actionDescriptor)
{
return $"{actionDescriptor.RouteValues["controller"]}.{actionDescriptor.RouteValues["action"]}";
}
}
Initialize filter in Startup class:
public void ConfigureServices(IServiceCollection services)
{
// code above
services.AddMvc(options =>
{
options.Filters.Add(typeof(ETagActionFilter));
});
services.AddScoped<ETagActionFilter>();
services.AddSingleton<ITagProvider, TagProvider>();
// code below
}
Use InvalidateETag method somewhere in controllers (in the place where you modifing data):
[HttpPost]
public async Task<ActionResult> Post([FromBody] SomeType data)
{
// TODO: Modify data
// Invalidate tag
var tag = $"{controllerName}.{methodName}"
_tagProvider.InvalidateETag(tag);
return NoContent();
}
This solution may require a change of a client side. If you are using fetch, you can use, for example, the following library: https://github.com/export-mike/f-etag.
P.S. I didn't specify an implementation of the ITagProvider interface, you will need to write your own.
P.P.S. Articles about ETag and caching: https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/http-caching, https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag
I am trying to call a method in the signalr Hub class from an (ASP.NET Core) MVC Controller, but I cannot find an example online that shows how to.
Note: There are lots of examples using older versions of signalr with the .Net Framework, but none that I can see that show how to do this in .Net Core.
I need to pass an id from the an MVC Action Result directly through to my Hub, without passing the id to the page, and then having to get a client connection back through to the hub.
public class ChatHub : Hub
{
public async Task DoSomething(int id)
{
//// Something in here.
}
}
public class HomeController : Controller
{
private readonly IHubContext<ChatHub> _hubContext;
public HomeController(IHubContext<ChatHub> hubContext)
{
_hubContext = hubContext;
}
public async Task<ActionResult> Index(int id)
{
//// Call the DoSomething method from here, passing the id across.
await _hubContext.Clients.All.SendAsync("AddToGroup", groupId);
}
}
Is there a way to do this please? (Or am I looking at this the wrong way, and is there a better way to achieve the same result?)
Update: If I pass the Id into the view, and then use JavaScript to call the Hub, this then calls the DoSomething method, so I can see it all hangs together correctly, but not when I try to call it in C#.
I think you're misunderstanding how it all works together (which is the same thing I did up until yesterday), the hub code is for the client-side script code to call back into and then action, whereas the IHubContext is used as the strongly typed methods that will be sent to the Client-side
Hub
// This class is used by the JavaScript Client to call into the .net core application.
public class ChatHub : Hub<IChatClient>
{
public static ConcurrentDictionary<string, string> Connections = new ConcurrentDictionary<string, string>();
// As an example, On connection save the user name with a link to the client Id for later user callback
public override Task OnConnectedAsync()
{
var user = Context.User.Identity.Name;
Connections.AddOrUpdate(this.Context.ConnectionId, user, (key, oldValue) => user);
return base.OnConnectedAsync();
}
public override Task OnDisconnectedAsync(Exception exception)
{
// Do something on disconnect.
}
// Add other methods you want to be able to call from JavaScript side in here...
public void SendMessage(int id, string message)
{
// Message doing stuff here.
}
}
ChatClient Interface
// This provides strongly-typed methods that you'll have on the Client side but these don't exist on the server.
public interface IChatClient
{
//So this method is a JS one not a .net one and will be called on the client(s)
Task DoSomething(int id);
Task NotificationUpdate(int id, string message);
}
Controller
public class HomeController : Controller
{
private readonly IHubContext<ChatHub, IChatClient> _hubContext;
public HomeController(IHubContext<ChatHub, IChatClient> hubContext)
{
_hubContext = hubContext;
}
public async Task<ActionResult> Index(int id)
{
// This calls the method on the Client-side
await _hubContext.Clients.All.DoSomething(id);
}
}
You can use the IHubContext to do this:
public class HomeController : Controller
{
private readonly IHubContext<ChatHub> _hubContext;
public HomeController(IHubContext<ChatHub> hubContext)
{
_hubContext = hubContext;
}
public async Task<ActionResult> Index(int id)
{
//// Call the DoSomething method from here, passing the id across.
await _hubContext.Clients.All.SendAsync("DoSomething", id);
}
}
Full docs and examples here.
I used the code here https://stackoverflow.com/a/53062957/6453193 for my HUB connection.
Since the js client-side code was not posted here. Just wanna share how I do it in js.
const connection = new signalR.HubConnectionBuilder()
.withUrl("/chatHub")
.configureLogging(signalR.LogLevel.Information)
.withAutomaticReconnect()
.build();
async function start() {
try {
await connection.start();
console.log("SignalR Connected.");
} catch (err) {
console.log(err);
setTimeout(start, 5000);
}
};
connection.onclose(async () => {
await start();
});
// Trigger if server-side code call it using NotificationUpdate
connection.on("NotificationUpdate", (message) => {
alert(message);
});
// Start the connection.
start();
and my controller notifying the client-side
await _hubContext.Clients.User(User.Identity.Name).NotificationUpdate($"This is an update");
I have a full engine that relies on abstractions based on user interactions. This works great with WPF/Xamarin app, cause I can implements this abstractions with window/form.
I have a little problem for porting this engine into ASP MVC.
A simple example can be show as this.
Abstraction interface (simplified)
public interface IQuestionBox
{
Task<bool> ShowYesNoQuestionBox(string message);
}
For WPF, it's really simple, I implement this interface as return the result of a window by calling ShowDialog().
In a simple business class, I can have this kind of calls (simplified) :
public async Task<string> GetValue(IQuestionBox qbox)
{
if(await qbox.ShowYesNoQuestionBox("Question ?"))
{
return "Ok";
}
return "NOk";
}
I really don't see how can I implement this kind of behavior in ASP, due to stateless of HTTP, knowing that this kind of call can be as various as domain/business need. The way I think it should be done is by returning a PartialView to inject into popup, but I don't see how to do this without breaking all the process ...
Anyone has ever done this ?
as I've said, I strongly doesn't recommend this pratice, but its possible, bellow the code that allows to do it, let's go:
To become it's possible I abused the use from TaskCompletionSource, this class allow us to set manually result in a task.
First we need to create a structure to encapsulate the process:
public class Process
{
// this dictionary store the current process running status, you will use it to define the future answer from the user interaction
private static Dictionary<string, Answare> StatusReport = new Dictionary<string, Answare>();
// this property is the secret to allow us wait for the ShowYesNoQuestion call, because til this happen the server doesn't send a response for the client.
TaskCompletionSource<bool> AwaitableResult { get; } = new TaskCompletionSource<bool>(true);
// here we have the question to interact with the user
IQuestionBox QuestionBox { get; set; }
// this method, receive your bussiness logical the receive your question as a parameter
public IQuestionBox Run(Action<IQuestionBox> action)
{
QuestionBox = new QuestionBox(this);
// here we create a task to execute your bussiness logical processment
Task.Factory.StartNew(() =>
{
action(QuestionBox);
});
// and as I said we wait the result from the processment
Task.WaitAll(AwaitableResult.Task);
// and return the question box to show the messages for the users
return QuestionBox;
}
// this method is responsable to register a question to receive future answers, as you can see, we are using our static dictionary to register them
public void RegisterForAnsware(string id)
{
if (StatusReport.ContainsKey(id))
return;
StatusReport.Add(id, new Answare()
{
});
}
// this method will deliver an answer for this correct context based on the id
public Answare GetAnsware(string id)
{
if (!StatusReport.ContainsKey(id))
return Answare.Empty;
return StatusReport[id];
}
// this method Releases the processment
public void Release()
{
AwaitableResult.SetResult(true);
}
// this method end the process delivering the response for the user
public void End(object userResponse)
{
if (!StatusReport.ContainsKey(QuestionBox.Id))
return;
StatusReport[QuestionBox.Id].UserResponse(userResponse);
}
// this method define the answer based on the user interaction, that allows the process continuing from where it left off
public static Task<object> DefineAnsware(string id, bool result)
{
if (!StatusReport.ContainsKey(id))
return Task.FromResult((object)"Success on the operation");
// here I create a taskcompletaionsource to allow get the result of the process, and send for the user, without it would be impossible to do it
TaskCompletionSource<object> completedTask = new TaskCompletionSource<object>();
StatusReport[id] = new Answare(completedTask)
{
HasAnswared = true,
Value = result
};
return completedTask.Task;
}
}
After that the question implementation
public interface IQuestionBox
{
string Id { get; }
Task<bool> ShowYesNoQuestionBox(string question);
HtmlString ShowMessage();
}
class QuestionBox : IQuestionBox
{
Process CurrentProcess { get; set; }
public string Id { get; } = Guid.NewGuid().ToString();
private string Question { get; set; }
public QuestionBox(Process currentProcess)
{
CurrentProcess = currentProcess;
CurrentProcess.RegisterForAnswer(this.Id);
}
public Task<bool> ShowYesNoQuestionBox(string question)
{
Question = question;
CurrentProcess.Release();
return AwaitForAnswer();
}
public HtmlString ShowMessage()
{
HtmlString htm = new HtmlString(
$"<script>showMessage('{Question}', '{Id}');</script>"
);
return htm;
}
private Task<bool> AwaitForAnswer()
{
TaskCompletionSource<bool> awaitableResult = new TaskCompletionSource<bool>(true);
Task.Factory.StartNew(() =>
{
while (true)
{
Thread.Sleep(2000);
var answare = CurrentProcess.GetAnswer(this.Id);
if (!answare.HasAnswered)
continue;
awaitableResult.SetResult(answare.Value);
break;
}
});
return awaitableResult.Task;
}
}
The differences for yours implementaion are:
1 - I create an Identifier to know for who I have to send the aswer, or just to stop the process.
2 - I receive a Process as parameter, because this allows us to call the method
CurrentProcess.Release(); in ShowYesNoQuestion, here in specific, releases the process to send the response responsable to interact with the user.
3 - I create the method AwaitForAnswer, here one more time we use from the TaskCompletionSource class. As you can see in this method we have a loop, this loop is responsable to wait for the user interaction, and til receive a response it doesn't release the process.
4 - I create the method ShowMessage that create a simple html script alert to simulate the user interaction.
Then a simple process class as you should be in your bussiness logical:
public class SaleService
{
public async Task<string> GetValue(IQuestionBox qbox)
{
if (await qbox.ShowYesNoQuestionBox("Do you think Edney is the big guy ?"))
{
return "I knew, Edney is the big guy";
}
return "No I disagree";
}
}
And then the class to represent the user answer
public class Answer
{
// just a sugar to represent empty responses
public static Answer Empty { get; } = new Answer { Value = true, HasAnswered = true };
public Answer()
{
}
// one more time abusing from TaskCompletionSource<object>, because with this guy we are abble to send the result from the process to the user
public Answer(TaskCompletionSource<object> completedTask)
{
CompletedTask = completedTask;
}
private TaskCompletionSource<object> CompletedTask { get; set; }
public bool Value { get; set; }
public bool HasAnswered { get; set; }
// this method as you can see, will set the result and release the task for the user
public void UserResponse(object response)
{
CompletedTask.SetResult(response);
}
}
Now we use all the entire structure create for this:
[HttpPost]
public IActionResult Index(string parametro)
{
// create your process an run it, passing what you want to do
Process process = new Process();
var question = process.Run(async (questionBox) =>
{
// we start the service
SaleService service = new SaleService();
// wait for the result
var result = await service.GetValue(questionBox);
// and close the process with the result from the process
process.End(result);
});
return View(question);
}
// here we have the method that deliver us the user response interaction
[HttpPost]
public async Task<JsonResult> Answer(bool result, string id)
{
// we define the result for an Id on the process
var response = await Process.DefineAnswer(id, result);
// get the response from process.End used bellow
// and return to the user
return Json(response);
}
and in your view
<!-- Use the question as the model page -->
#model InjetandoInteracaoComUsuario.Controllers.IQuestionBox
<form asp-controller="Home" asp-action="Index">
<!-- create a simple form with a simple button to submit the home -->
<input type="submit" name="btnDoSomething" value="All about Edney" />
</form>
<!-- in the scripts section we create the function that we call on the method ShowMessage, remember?-->
<!-- this method request the action answer passing the questionbox id, and the result from a simple confirm -->
<!-- And to finalize, it just show an alert with the process result -->
#section scripts{
<script>
function showMessage(message, id) {
var confirm = window.confirm(message);
$.post("/Home/Answer", { result: confirm, id: id }, function (e) {
alert(e);
})
}
</script>
#Model?.ShowMessage()
}
As I've said, I realy disagree with this pratices, the correct should to write a new dll, to support the web enviroment, but I hope it help you.
I put the project on github to you can download an understand all the solution
I realy hope it can help you
You can create a web socket connection from client side to server side. And work with front-end content with web socket request. It could be implemented as following:
Client side:
$app = {
uiEventsSocket : null,
initUIEventsConnection : function(url) {
//create a web socket connection
if (typeof (WebSocket) !== 'undefined') {
this.uiEventsSocket = new WebSocket(url);
} else if (typeof (MozWebSocket) !== 'undefined') {
this.uiEventsSocket = new MozWebSocket(url);
} else {
console.error('WebSockets unavailable.');
}
//notify if there is an web socket error
this.uiEventsSocket.onerror = function () {
console.error('WebSocket raised error.');
}
this.uiEventsSocket.onopen = function () {
console.log("Connection to " + url + " established");
}
//handling message from server side
this.uiEventsSocket.onmessage = function (msg) {
this._handleMessage(msg.data);
};
},
_handleMessage : function(data){
//the message should be in json format
//the next line fails if it is not
var command = JSON.parse(data);
//here is handling the request to show prompt
if (command.CommandType == 'yesNo') {
var message = command.Message;
var result = confirm(message);
//not sure that bool value will be successfully converted
this.uiEventsSocket.send(result ? "true" : "false");
}
}
}
And init it from ready or load event:
window.onload = function() { $app.initUIEventsConnection(yourUrl); }
Note that you url should begin with ws:// instead of http:// and wss:// instead of https:// (Web Sockets and Web Sockets Secure).
Server side.
Here is a good article for how to setup web sockets at asp.net core application or you could find another one. Note that you should group web socket connections from single user and if you want to send a message to the concrete user, you should send message for every connection from this user.
Every web socket you should accept with AcceptWebSocketAsync() method call and then add instance of this web socket to singleton, which contains a set of web sockets connection groupped by user.
The following class will be used to operate commands:
public class UICommand
{
public string CommandType { get; set; }
public string Message { get; set; }
public Type ReturnType { get; set; }
}
And a full code of singleton for handling sockets
public class WebSocketsSingleton
{
private static WebSocketsSingleton _instance = null;
//here stored web sockets groupped by user
//you could use user Id or another marker to exactly determine the user
private Dictionary<string, List<WebSocket>> _connectedSockets;
//for a thread-safety usage
private static readonly ReaderWriterLockSlim Locker = new ReaderWriterLockSlim();
public static WebSocketsSingleton Instance {
get {
if (this._instance == null)
{
this._instance = new WebSocketsSingleton();
}
return this._instance;
}
}
private WebSocketsSingleton()
{
this._connectedSockets = new Dictionary<string, List<WebSocket>>();
}
/// <summary>
/// Adds a socket into the required collection
/// </summary>
public void AddSocket(string userName, WebSocket ws)
{
if (!this._connectedSockets.ContainsKey(userName))
{
Locker.EnterWriteLock();
try
{
this._connectedSockets.Add(userName, new List<WebSocket>());
}
finally
{
Locker.ExitWriteLock();
}
}
Locker.EnterWriteLock();
try
{
this._connectedSockets[userName].Add(ws);
}
finally
{
Locker.ExitWriteLock();
}
}
/// <summary>
/// Sends a UI command to required user
/// </summary>
public async Task<string> SendAsync(string userName, UICommand command)
{
if (this._connectedSockets.ContainsKey(userName))
{
var sendData = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(command));
foreach(var item in this._connectedSockets[userName])
{
try
{
await item.SendAsync(new ArraySegment<byte>(sendData), WebSocketMessageType.Text, true, CancellationToken.None);
}
catch (ObjectDisposedException)
{
//socket removed from front end side
}
}
var buffer = new ArraySegment<byte>(new byte[1024]);
var token = CancellationToken.None;
foreach(var item in this._connectedSockets[userName])
{
await Task.Run(async () => {
var tempResult = await item.ReceiveAsync(buffer, token);
//result received
token = new CancellationToken(true);
});
}
var resultStr = Encoding.Utf8.GetString(buffer.Array);
if (command.ReturnType == typeof(bool))
{
return resultStr.ToLower() == "true";
}
//other methods to convert result into required type
return resultStr;
}
return null;
}
}
Explanation:
on establishing connection from web socket it will be added with
AddSocket method
on sending request to show a message, the required command will be passed into SendAsync method
the command will be serialized to JSON (using Json.Net, however you could serialize in your way) and send to all sockets, related to the required user
after the command sent, application will wait for respond from front end side
the result will be converted to required type and sent back to your IQuestionBox
In the web socket handling your should add some kind of the following code:
app.Use(async (http, next) =>
{
if (http.WebSockets.IsWebSocketRequest)
{
var webSocket = await http.WebSockets.AcceptWebSocketAsync();
var userName = HttpContext.Current.User.Identity.Name;
WebSocketsSingleton.Instance.AddSocket(userName, webSocket);
while(webSocket.State == WebSocketState.Open)
{
//waiting till it is not closed
}
//removing this web socket from the collection
}
});
And your method implementation of ShowYesNoQuestionBox should be kind of following:
public async Task<bool> ShowYesNoQuestionBox(string userName, string text)
{
var command = new UICommand
{
CommandType = "yesNo",
Message = text,
ReturnType = typeof(bool)
};
return await WebSocketsSingleton.Instance.SendAsync(string userName, command);
}
Note that there should be added userName to prevent sending the same message to all of the connected users.
WebSocket should create the persistent connection between server and client sides, so you could simply send commands in two ways.
I am kindly new to Asp.Net Core, so the final implementation could be a bit different from this.
It's actually much the same, except your UI is sort of disconnected and proxied with the HTTP protocol for the most part.
you essentially need to build the same code as your WPF code but then in the browser construct ajax calls in to the controller actions to apply your logic.
To clarify ...
so lets say you are building up a process over a series of questions that based on the users answer you put different steps in to the process.
You can either ...
build the process in the database
build it in session on the server
build it on the client as a js object
then do a post for execution ofthe constructed process.
think of the "statelessness" as a series of short interactions, but the state you keep between them can be done either on the client, in a db or in the users logged in session on the web server.
In your controller you can add an ActionResult that will give you the html response to your jquery modal popup request. Here is an example
public class MController : Controller {
public ActionResult doWork(requirement IQuestionBox)
{
// model is already modelBound/IOC resolved
return PartialView("_doWork", requirement );
}
}
//scripts
$(function(){
$.ajax({
url:"/m/doWork",
type:"get",
success:function(data){
$modal.html(data); // bind to modal
}
});
});
Apologies for not fully understanding the question.
hope this helps!
I write web application using ASP.NET MVC WebAPI and I want to transform current synchronous code to asynchronous for optimization. Problem is that I fill ViewModel with multiple objects taken from repository. These calls from repository should be async.
Let's asume I have signature for repository calls respecting this interface
public interface ICompanyRepository
{
IEnumerable<Company> GetCompanies();
IEnumerable<Address> GetAddresses();
}
ViewModels definition
public class CompaniesFullViewModel
{
public IEnumerable<Company> Companies { get; set; }
public IEnumerable<Address> Addresses { get; set; }
}
And controller:
public class CompanyController
{
public readonly ICompanyRepository Repository { get; private set; }
public CompanyController(IRepository repository)
{
Repository = repository;
}
[ResponseType(typeof(CompaniesFullViewModel))]
public HttpResponseMessage Get()
{
var companies = Repository.GetCompanies();
var addresses = Repository.GetAddresses();
HttpStatusCode statusCode = companies.Any()
? HttpStatusCode.OK
: HttpStatusCode.PartialContent;
return
Request.CreateResponse(
statusCode,
new CompaniesFullViewModel
{
Companies = companies,
Addresses = addresses
});
}
}
Furthermore I have tests implemented to the controller:
[TestClass]
public sealed class CompanyTestController : BaseTestController
{
#region Fields
private static Mock<ICompanyRepository> _repositoryMock;
private static CompanyController _controller;
#endregion
[ClassInitialize]
public static void Initialize(TestContext testContext)
{
// Mock repository
_repositoryMock = new Mock<ICompanyRepository>();
DependencyResolver.Default.Container.RegisterInstance(_repositoryMock.Object);
// Create controller
_controller =
DependencyResolver.Default.Container.Resolve<CompanyController>();
// Init request
_controller.Request = new HttpRequestMessage();
_controller.Request.SetConfiguration(new HttpConfiguration());
}
[ClassCleanup]
public static void Cleanup()
{
_controller.Dispose();
}
[TestMethod]
public void Get_ActionExecutes_ReturnsEmptyCompaniesViewModel()
{
var companies = new List<Company>();
var addresses = new List<Address>();
// Setup fake method
_repositoryMock
.Setup(c => c.GetCompanies())
.Returns(companies);
_repositoryMock
.Setup(c => c.GetAddresses())
.Returns(addresses);
// Execute action
var response = _controller.Get();
// Check the response
Assert.AreEqual(HttpStatusCode.PartialContent, response.StatusCode);
}
}
How can I convert the controller to async, if the repository is async and the signature looks like this:
public interface ICompanyRepository
{
Task<IEnumerable<Company>> GetCompaniesAsync();
Task<IEnumerable<Address>> GetAddressesAsync();
}
What you need to do is change the Controller action to be async as well, and change the return type to Task<>. You can then await your asynchronous repository calls:
[ResponseType(typeof(CompaniesFullViewModel))]
public async Task<HttpResponseMessage> Get() // async keyword.
{
var companies = await Repository.GetCompaniesAsync(); // await
var addresses = await Repository.GetAddressesAsync(); // await
HttpStatusCode statusCode = companies.Any()
? HttpStatusCode.OK
: HttpStatusCode.PartialContent;
return
Request.CreateResponse(
statusCode,
new CompaniesFullViewModel
{
Companies = companies,
Addresses = addresses
});
}
By convention, you can also change the name of the controller action to end in Async as well, although if you are using RESTful conventions and / or Routing attributes, the actual name of the controller action isn't really important.
Testing
I use XUnit and NUnit, but it seems MSTest also supports testing of asynchronous methods, and Moq also provides Async versions of the setups:
[Test]
public async Task Get_ActionExecutes_ReturnsEmptyCompaniesViewModel() // async Task
{
var companies = new List<Company>();
var addresses = new List<Address>();
// Setup fake method
_repositoryMock
.Setup(c => c.GetCompaniesAsync())
.ReturnsAsync(companies); // Async
_repositoryMock
.Setup(c => c.GetAddressesAsync())
.ReturnsAsync(addresses); // Async
// Execute action
var response = await _controller.Get(); // Await
// Check the response
Assert.AreEqual(HttpStatusCode.PartialContent, response.StatusCode);
_repositoryMock.Verify(m => m.GetAddressesAsync(), Times.Once);
_repositoryMock.Verify(m => m.GetCompaniesAsync(), Times.Once);
}
As an aside, it seems you are using Setter Dependency injection. An alternative is to use Constructor injection, which has the benefit of ensuring that the class is always in a valid state (i.e. there is no transient state while it is waiting for the dependencies to be set). This also allows the dependencies (your repository in this case) to be made readonly.
I'm still trying to wrap my head around async and I'm wondering why the following code is causing a deadlock. My use case is this: I have a service interface which attempts to abstract how the service is implemented. One of the services is an OAuth based web-service. The service interface has a method Connect() which anyone using the interface must do prior to using it.
On my client side I create my concrete service object and call Connect() in my view constructor (this is a prototype, so I'm just trying to get a proof of concept going). In the OAuth-based service, the connect call requires retrieving an access token, so it (attempts) to do this asynchronously. This Connect() call never returns, though, and the application is deadlocked (but the UI is active). I'm guessing I'm messing up and trying to synchronously use my client somewhere, but I'm not sure where.
Control
public class MainWindow
{
public MainWindow()
{
InitializeComponent();
_webService = new OAuthBasedWebService();
_webService.ShowAuthorizationPage += _webService_ShowAuthorizationPage; // this is defined on the concrete object -- i know, bad design
_webService.Connect();
}
}
OAuth based webservice
public class OAuthBasedWebService()
{
private OAuthWrapper _wrapper;
public async void Connect()
{
var uri = await _wrapper.GetAuthorizationUri();
OnShowAuthorizationPage(uri);
}
}
internal class OAuthWrapper
{
public async Task<Uri> GetAuthorizationUri()
{
var uri = await _consumer.GetAuthorizationUriAsync();
return uri;
}
}
internal class OAuthConsumer
{
public async Task<Uri> GetAuthorizationUriAsync()
{
using (var client = new HttpClient())
{
client.BaseAddress = "webservicebaseaddress";
var content = new FormUrlEncodedContent(new []
{
CreateParameter("oauth_consumer_key", "consumerkey"),
CreateParameter("oauth_consumer_secret", "consumersecret")
// etc., etc.
});
var response = await client.PostAsync("/method_path", content).ConfigureAwait(false);
var responseContent = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
// parse authorization uri from responseContent
return authorizationUri;
}
}
}
I know the design needs a little work but I'm trying to figure out why this is deadlocking. I'm guessing it is because _webService.Connect() is not being called asynchronously but I also cannot await that because it doesn't return anything and the rest of the program doesn't depend on it.
I'm not sure why you are using a event here, if the problem was just because you couldn't make the constructor "async" then just move the conect call to another method:
public class MainWindow
{
public MainWindow()
{
InitializeComponent();
Init();
}
public async void Init(){
_webService = new OAuthBasedWebService();
Uri uri=await _webService.Connect();
_webService_ShowAuthorizationPage(uri);
}
}
public class OAuthBasedWebService()
{
private OAuthWrapper _wrapper;
public async Task<Uri> Connect()
{
return await _wrapper.GetAuthorizationUri();
}
}