Verbrauch eines ASP.NET-Web-API-Aufrufs in einem MVC-Controller

84370
ledgeJumper

In einem neuen Projekt, das ich für meine Arbeit erstelle, erstelle ich eine ziemlich große ASP.NET-Web-API. Die API befindet sich in einer separaten Visual Studio-Lösung, die auch alle meine Geschäftslogik, Datenbankinteraktionen und ModelKlassen enthält.

In der von mir erstellten Testanwendung (ASP.NET MVC4) möchte ich in der Lage sein, eine von dem Steuerelement definierte API-URL zu treffen und die Rückgabe-JSON in eine ModelKlasse umzuwandeln . Der Grund dahinter ist, dass ich die Möglichkeit nutzen möchte, meine Ansichten stark zu tippen Model. Dies ist alles noch in einem Proof-of-Concept-Stadium, daher habe ich keine Leistungstests durchgeführt, aber ich bin neugierig, ob das, was ich mache, eine gute Praxis ist oder ob ich verrückt bin, diesen Weg zu gehen.

Hier ist der Code auf dem Client Controller:

public class HomeController : Controller
{
    protected string dashboardUrlBase = "http://localhost/webapi/api/StudentDashboard/";

    public ActionResult Index() //This view is strongly typed against User
    {
        //testing against Joe Bob
        string adSAMName = "jBob";
        WebClient client = new WebClient();
        string url = dashboardUrlBase + "GetUserRecord?userName=" + adSAMName;
        //'User' is a Model class that I have defined.
        User result = JsonConvert.DeserializeObject<User>(client.DownloadString(url));
        return View(result);
    }
. . .
}

Wenn ich mich für diese Route entschieden habe, lade ich einige Teilansichten auf dieser Seite (wie ich es auch auf den nächsten Seiten tun werde). Die Teilansichten werden über einen $.ajaxAufruf geladen, der auf diesen Controller trifft und im Wesentlichen dasselbe wie der obige Code ausführt:

  1. Instantiiere ein neues WebClient
  2. Definieren Sie die zu treffende URL
  3. Deserialisieren Sie das Ergebnis und wandeln Sie es in eine ModelKlasse um

Es ist also möglich (und wahrscheinlich auch möglich), dass ich dieselbe Aktion 4-5 Mal für eine einzelne Seite durchführe.

Gibt es eine bessere Methode, dies zu tun:

  1. Lassen Sie mich stark typisierte Ansichten behalten
  2. Erledige meine Arbeit auf dem Server und nicht auf dem Client (dies ist nur eine Präferenz, da ich C # schneller schreiben kann als ich JavaScript schreibe.)
Antworten
17
Diese Methode hilft wahrscheinlich Ihrer Ursache: http://i-skool.co.uk/net/re-usable-webapi-request-wrapper/ Lassen Sie uns eine API-Route aufrufen und das delegierte Objekt zurückgeben. Martin Dixon vor 6 Jahren 0

2 Antworten auf die Frage

6
Peter Kiss

Testing?

Then create a simple unit test and test your API controller or your business layer classes directly. If you want to create an integration test or need a cleaner solution in production code read further.

Wrapping the API calls

This is just a disposable wrapper around the WebClient which can be easily reused.

public abstract class WebClientWrapperBase : IDisposable
{
    private readonly string _baseUrl;
    private Lazy<WebClient> _lazyClient;

    protected WebClientWrapperBase(string baseUrl)
    {
        _baseUrl = baseUrl.Trim('/');
        _lazyClient = new Lazy<WebClient>(() => new WebClient());
    }

    protected WebClient Client()
    {
        if (_lazyClient == null)
        {
            throw new ObjectDisposedException("WebClient has been disposed");
        }

        return _lazyClient.Value;
    }

    protected T Execute<T>(string urlSegment)
    {
        return JsonConvert.DeserializeObject<T>(Client().DownloadString(_baseUrl + '/' + urlSegment.TrimStart('/')));
    }

    ~WebClientWrapperBase()
    {
        Dispose(false);
    }

    public void Dispose()
    {
        Dispose(false);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (_lazyClient != null)
        {
            if (disposing)
            {
                if (_lazyClient.IsValueCreated)
                {
                    _lazyClient.Value.Dispose();
                    _lazyClient = null;
                }
            }

            // There are no unmanaged resources to release, but
            // if we add them, they need to be released here.
        }
    }
}

Creating a "strongly typed proxy":

class StudentDashboardClient : WebClientWrapperBase
{
    public StudentDashboardClient()
        : base("http://localhost/webapi/api/StudentDashboard/")
    {
        //just for compatibility
    }

    public StudentDashboardClient(string baseUrl)
        : base(baseUrl)
    {
    }

    public User GetUserRecord(string userName)
    {
        return Execute<User>("GetUserRecord?userName=" + userName);
    }
}

And then passing to your controllers where it's needed:

public class HomeController : Controller
{
    private readonly StudentDashboardClient _studentDashboardClient;

    public HomeController(StudentDashboardClient studentDashboardClient)
    {
        _studentDashboardClient = studentDashboardClient;
    }

    public ActionResult Index()
    {
        return View(_studentDashboardClient.GetUserRecord("jBob"));
    }

    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            _studentDashboardClient.Dispose();
        }
        base.Dispose(disposing);
    }
}

Note that the controller now have a parameterless constructor so you will need a solution to instantiate controllers this way for example a DI framework with MVC support like Ninject.

What you gain? Cleaner code.

Ich mag den Wrapper sehr. Einfach aber erledigt der Job, den das OP scheinbar will dreza vor 6 Jahren 0
Kümmert sich dieser Wrapper auch um POSTs? juanreyesv vor 4 Jahren 0
3
Patryk Ćwiek

All the controlers are regular classes with methods.

Assuming your API controller StudentDashboard has a Get(string name) verb method, you can do this:

public ActionResult Index() //This view is strongly typed against User
{
    //testing against Joe Bob
    string adSAMName = "jBob";
    var apiController = new StudentDashboardController(); //or whatever other API controller returns your data
    var result = apiController.Get(adSAMName);
    return View(result);
}

This should give you strong typing. You can also instantiate and use 'regular' MVC controllers too.

[EDIT]

Per comment, it's even better if you delegate the controller creation to the framework

ControllerBuilder.GetControllerFactory().CreateController(Request.RequestContext, controllerName);

You can either set the string or extract it from the type if you want stronger typing.

Reference MSDN

Ich denke, ich würde die Verwendung des Frameworks in Betracht ziehen, um den Controller auf diese Weise zu machen, und DI wird für Sie sortiert, z. B. ControllerBuilder.Current.GetControllerFactory (). CreateController (Request.RequestContext, controllerName) dreza vor 6 Jahren 2
@dreza Sehr guter Punkt, ich werde diese Möglichkeit hinzufügen. Patryk Ćwiek vor 6 Jahren 0